跳转至

数据层 API

DataSourceAdapter

Bases: ABC


              flowchart TD
              fund_cli.data.base.DataSourceAdapter[DataSourceAdapter]

              

              click fund_cli.data.base.DataSourceAdapter href "" "fund_cli.data.base.DataSourceAdapter"
            

数据源适配器基类

所有数据源适配器必须继承此类并实现所有抽象方法。

源代码位于: src/fund_cli/data/base.py
  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
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
class DataSourceAdapter(ABC):
    """
    数据源适配器基类

    所有数据源适配器必须继承此类并实现所有抽象方法。
    """

    def __init__(self, name: str):
        """
        初始化数据源适配器

        Args:
            name: 数据源名称
        """
        self._name = name

    @property
    def name(self) -> str:
        """数据源名称"""
        return self._name

    @abstractmethod
    def is_available(self) -> bool:
        """
        检查数据源是否可用

        Returns:
            数据源是否可用
        """
        pass

    # =========================================================================
    # P0 - 核心基金功能接口 (18个)
    # =========================================================================

    # ----- 基金基本信息 (5个,含1个已有) -----
    @abstractmethod
    def get_fund_info(self, fund_code: str) -> dict[str, Any]:
        """
        获取基金基础信息

        Args:
            fund_code: 基金代码(6位数字)

        Returns:
            基金基础信息字典

        Raises:
            DataNotFoundError: 基金不存在
            DataSourceError: 数据源错误
        """
        pass

    @abstractmethod
    def get_all_fund_names(self) -> pd.DataFrame:
        """
        获取所有基金名称列表

        Returns:
            DataFrame包含:基金代码, 拼音缩写, 基金简称, 基金类型, 拼音全称
        """
        pass

    @abstractmethod
    def get_fund_info_ths(self, fund_code: str) -> dict[str, Any]:
        """
        同花顺-基金基本信息

        Args:
            fund_code: 基金代码

        Returns:
            基金详细信息字典
        """
        pass

    @abstractmethod
    def get_index_fund_info(
        self, category: str = "全部", indicator: str = "全部"
    ) -> pd.DataFrame:
        """
        东方财富-指数型基金基本信息

        Args:
            category: 分类,可选"全部","沪深指数","行业主题","大盘指数"等
            indicator: 指标,可选"全部","被动指数型","增强指数型"

        Returns:
            指数型基金信息DataFrame
        """
        pass

    @abstractmethod
    def get_fund_overview(self, fund_code: str) -> dict[str, Any]:
        """
        天天基金-基金档案基本概况

        Args:
            fund_code: 基金代码

        Returns:
            基金概况字典
        """
        pass

    # ----- 基金申购状态 (1个) -----
    @abstractmethod
    def get_fund_purchase_status(self) -> pd.DataFrame:
        """
        东方财富-基金申购/赎回状态

        Returns:
            DataFrame包含:基金代码, 基金简称, 申购状态, 赎回状态等
        """
        pass

    # ----- 基金净值数据 (2个,含1个已有) -----
    @abstractmethod
    def get_fund_nav(
        self,
        fund_code: str,
        start_date: date | None = None,
        end_date: date | None = None,
    ) -> pd.DataFrame:
        """
        获取基金净值数据

        Args:
            fund_code: 基金代码
            start_date: 开始日期
            end_date: 结束日期

        Returns:
            净值数据DataFrame
        """
        pass

    @abstractmethod
    def get_fund_daily_nav(self) -> pd.DataFrame:
        """
        东方财富-开放式基金每日净值(全部)

        Returns:
            全部基金净值DataFrame
        """
        pass

    # ----- 基金行情数据 (8个) -----
    @abstractmethod
    def get_etf_spot(self) -> pd.DataFrame:
        """
        东方财富-ETF实时行情(全部)

        Returns:
            ETF实时行情DataFrame
        """
        pass

    @abstractmethod
    def get_fund_category_spot(
        self, category: str = "", date: str | None = None
    ) -> pd.DataFrame:
        """
        同花顺-基金实时行情(按类型)

        Args:
            category: 基金类型,如"股票型","债券型","混合型","ETF","LOF"等
            date: 日期

        Returns:
            基金行情DataFrame
        """
        pass

    @abstractmethod
    def get_etf_spot_ths(self, date: str | None = None) -> pd.DataFrame:
        """
        同花顺-ETF实时行情

        Args:
            date: 日期

        Returns:
            ETF行情DataFrame
        """
        pass

    @abstractmethod
    def get_lof_spot(self) -> pd.DataFrame:
        """
        东方财富-LOF实时行情(全部)

        Returns:
            LOF实时行情DataFrame
        """
        pass

    @abstractmethod
    def get_etf_hist(
        self,
        fund_code: str,
        period: str = "daily",
        start_date: str | None = None,
        end_date: str | None = None,
    ) -> pd.DataFrame:
        """
        东方财富-ETF历史行情

        Args:
            fund_code: 基金代码
            period: 周期,"daily","weekly","monthly"
            start_date: 开始日期
            end_date: 结束日期

        Returns:
            ETF历史行情DataFrame
        """
        pass

    @abstractmethod
    def get_lof_hist(
        self,
        fund_code: str,
        period: str = "daily",
        start_date: str | None = None,
        end_date: str | None = None,
    ) -> pd.DataFrame:
        """
        东方财富-LOF历史行情

        Args:
            fund_code: 基金代码
            period: 周期
            start_date: 开始日期
            end_date: 结束日期

        Returns:
            LOF历史行情DataFrame
        """
        pass

    @abstractmethod
    def get_etf_minute(
        self,
        fund_code: str,
        period: str = "1",
        start_date: str | None = None,
        end_date: str | None = None,
    ) -> pd.DataFrame:
        """
        东方财富-ETF分时行情

        Args:
            fund_code: 基金代码
            period: 分钟周期,"1","5","15","30","60"
            start_date: 开始日期
            end_date: 结束日期

        Returns:
            ETF分时行情DataFrame
        """
        pass

    @abstractmethod
    def get_lof_minute(
        self,
        fund_code: str,
        period: str = "1",
        start_date: str | None = None,
        end_date: str | None = None,
    ) -> pd.DataFrame:
        """
        东方财富-LOF分时行情

        Args:
            fund_code: 基金代码
            period: 分钟周期
            start_date: 开始日期
            end_date: 结束日期

        Returns:
            LOF分时行情DataFrame
        """
        pass

    # ----- 基金持仓数据 (4个,含1个已有) -----
    @abstractmethod
    def get_fund_holdings(
        self,
        fund_code: str,
        report_date: date | None = None,
    ) -> pd.DataFrame:
        """
        获取基金持仓数据

        Args:
            fund_code: 基金代码
            report_date: 报告日期

        Returns:
            持仓数据DataFrame
        """
        pass

    @abstractmethod
    def get_fund_bond_holdings(
        self, fund_code: str, year: int | None = None
    ) -> pd.DataFrame:
        """
        天天基金-基金债券持仓

        Args:
            fund_code: 基金代码
            year: 年份

        Returns:
            债券持仓DataFrame
        """
        pass

    @abstractmethod
    def get_fund_industry_allocation(
        self, fund_code: str, year: int | None = None
    ) -> pd.DataFrame:
        """
        天天基金-行业配置

        Args:
            fund_code: 基金代码
            year: 年份

        Returns:
            行业配置DataFrame
        """
        pass

    @abstractmethod
    def get_fund_portfolio_change(
        self, fund_code: str, indicator: str = "累计买入", year: int | None = None
    ) -> pd.DataFrame:
        """
        天天基金-重大变动(累计买入/卖出)

        Args:
            fund_code: 基金代码
            indicator: "累计买入"或"累计卖出"
            year: 年份

        Returns:
            持仓变动DataFrame
        """
        pass

    # ----- 基金经理 (1个) -----
    @abstractmethod
    def get_all_fund_managers(self) -> pd.DataFrame:
        """
        天天基金-基金经理大全

        Returns:
            基金经理信息DataFrame
        """
        pass

    # ----- 搜索/列表功能 (已有) -----
    @abstractmethod
    def search_funds(
        self,
        fund_type: str | None = None,
        company: str | None = None,
        min_scale: float | None = None,
        max_scale: float | None = None,
        keyword: str | None = None,
        limit: int = 100,
    ) -> pd.DataFrame:
        """
        搜索/筛选基金

        Args:
            fund_type: 基金类型
            company: 基金公司
            min_scale: 最小规模(亿元)
            max_scale: 最大规模(亿元)
            keyword: 关键词搜索
            limit: 返回数量限制

        Returns:
            基金列表DataFrame
        """
        pass

    @abstractmethod
    def get_fund_list(self, fund_type: str | None = None) -> pd.DataFrame:
        """
        获取基金列表

        Args:
            fund_type: 基金类型筛选

        Returns:
            基金列表DataFrame
        """
        pass

    # ----- 基准指数 (已有) -----
    @abstractmethod
    def get_benchmark_nav(
        self,
        benchmark_code: str,
        start_date: date | None = None,
        end_date: date | None = None,
    ) -> pd.DataFrame:
        """
        获取基准指数净值数据

        Args:
            benchmark_code: 基准指数代码
            start_date: 开始日期
            end_date: 结束日期

        Returns:
            基准净值数据DataFrame
        """
        pass

    # ----- 费率 (已有) -----
    @abstractmethod
    def get_fund_fee(self, fund_code: str) -> dict[str, Any]:
        """
        获取基金费率信息

        Args:
            fund_code: 基金代码

        Returns:
            费率信息字典
        """
        pass

    # =========================================================================
    # P1 - 分析增强功能接口 (25个)
    # =========================================================================

    # ----- 基金公司/规模 (5个) -----
    @abstractmethod
    def get_fund_company_aum(self) -> pd.DataFrame:
        """
        东方财富-基金公司管理规模排名

        Returns:
            基金公司规模排名DataFrame
        """
        pass

    @abstractmethod
    def get_fund_aum_trend(self) -> pd.DataFrame:
        """
        东方财富-基金市场管理规模走势

        Returns:
            规模走势DataFrame
        """
        pass

    @abstractmethod
    def get_fund_company_aum_history(self, year: int | None = None) -> pd.DataFrame:
        """
        东方财富-基金公司历年管理规模排行

        Args:
            year: 年份

        Returns:
            历年规模排行DataFrame
        """
        pass

    @abstractmethod
    def get_fund_scale_change(self) -> pd.DataFrame:
        """
        天天基金-规模变动(全市场汇总)

        Returns:
            规模变动DataFrame
        """
        pass

    @abstractmethod
    def get_fund_holder_structure(self) -> pd.DataFrame:
        """
        天天基金-持有人结构(全市场汇总)

        Returns:
            持有人结构DataFrame
        """
        pass

    # ----- 基金评级 (4个) -----
    @abstractmethod
    def get_fund_ratings(self) -> pd.DataFrame:
        """
        天天基金-基金评级总汇

        Returns:
            基金评级DataFrame
        """
        pass

    @abstractmethod
    def get_fund_rating_sh(self, date: str | None = None) -> pd.DataFrame:
        """
        天天基金-上海证券评级

        Args:
            date: 日期(YYYYMMDD)

        Returns:
            上海证券评级DataFrame
        """
        pass

    @abstractmethod
    def get_fund_rating_zs(self, date: str | None = None) -> pd.DataFrame:
        """
        天天基金-招商证券评级

        Args:
            date: 日期(YYYYMMDD)

        Returns:
            招商证券评级DataFrame
        """
        pass

    @abstractmethod
    def get_fund_rating_ja(self, date: str | None = None) -> pd.DataFrame:
        """
        天天基金-济安金信评级

        Args:
            date: 日期(YYYYMMDD)

        Returns:
            济安金信评级DataFrame
        """
        pass

    # ----- 基金分红/拆分 (3个) -----
    @abstractmethod
    def get_fund_dividends(
        self,
        year: int | None = None,
        fund_type: str = "",
        page: int = -1,
    ) -> pd.DataFrame:
        """
        天天基金-基金分红

        Args:
            year: 年份
            fund_type: 基金类型
            page: 页码,-1为全部

        Returns:
            分红数据DataFrame
        """
        pass

    @abstractmethod
    def get_fund_splits(
        self,
        year: int | None = None,
        fund_type: str = "",
        page: int = -1,
    ) -> pd.DataFrame:
        """
        天天基金-基金拆分

        Args:
            year: 年份
            fund_type: 基金类型
            page: 页码

        Returns:
            拆分数据DataFrame
        """
        pass

    @abstractmethod
    def get_fund_dividend_rank(self) -> pd.DataFrame:
        """
        天天基金-基金累计分红排行

        Returns:
            分红排行DataFrame
        """
        pass

    # ----- 基金排行 (5个) -----
    @abstractmethod
    def get_fund_rank_by_type(self, fund_type: str = "全部") -> pd.DataFrame:
        """
        东方财富-开放式基金排行

        Args:
            fund_type: 基金类型,"全部","股票型","混合型","债券型"等

        Returns:
            基金排行DataFrame
        """
        pass

    @abstractmethod
    def get_exchange_fund_rank(self) -> pd.DataFrame:
        """
        东方财富-场内交易基金排行

        Returns:
            场内基金排行DataFrame
        """
        pass

    @abstractmethod
    def get_money_fund_rank(self) -> pd.DataFrame:
        """
        东方财富-货币型基金排行

        Returns:
            货币基金排行DataFrame
        """
        pass

    @abstractmethod
    def get_lcx_fund_rank(self) -> pd.DataFrame:
        """
        东方财富-理财基金排行

        Returns:
            理财基金排行DataFrame
        """
        pass

    @abstractmethod
    def get_hk_fund_rank(self) -> pd.DataFrame:
        """
        东方财富-香港基金排行

        Returns:
            香港基金排行DataFrame
        """
        pass

    # ----- 基金业绩/分析 (3个) -----
    @abstractmethod
    def get_fund_achievement(self, fund_code: str) -> pd.DataFrame:
        """
        雪球-基金业绩(年度+阶段)

        Args:
            fund_code: 基金代码

        Returns:
            业绩数据DataFrame
        """
        pass

    @abstractmethod
    def get_fund_risk_analysis(self, fund_code: str) -> pd.DataFrame:
        """
        雪球-基金数据分析(夏普/回撤等)

        Args:
            fund_code: 基金代码

        Returns:
            风险分析DataFrame
        """
        pass

    @abstractmethod
    def get_fund_profit_probability(self, fund_code: str) -> pd.DataFrame:
        """
        雪球-基金盈利概率

        Args:
            fund_code: 基金代码

        Returns:
            盈利概率DataFrame
        """
        pass

    # ----- 基金资产配置 (1个) -----
    @abstractmethod
    def get_fund_asset_allocation(
        self, fund_code: str, date: str | None = None
    ) -> pd.DataFrame:
        """
        雪球-基金资产配置

        Args:
            fund_code: 基金代码
            date: 财报日期(YYYYMMDD)

        Returns:
            资产配置DataFrame
        """
        pass

    # ----- 市场指数扩展 (6个) -----
    @abstractmethod
    def get_index_spot_em(self, category: str = "沪深重要指数") -> pd.DataFrame:
        """
        东财-沪深京指数实时行情

        Args:
            category: 指数分类

        Returns:
            指数行情DataFrame
        """
        pass

    @abstractmethod
    def get_index_spot_sina(self) -> pd.DataFrame:
        """
        新浪-中国股票指数实时行情

        Returns:
            指数行情DataFrame
        """
        pass

    @abstractmethod
    def get_index_daily_tx(
        self, code: str, start: str | None = None, end: str | None = None
    ) -> pd.DataFrame:
        """
        腾讯-指数历史行情

        Args:
            code: 指数代码
            start: 开始日期
            end: 结束日期

        Returns:
            指数历史DataFrame
        """
        pass

    @abstractmethod
    def get_index_daily_em(
        self, code: str, start: str | None = None, end: str | None = None
    ) -> pd.DataFrame:
        """
        东财-指数历史行情

        Args:
            code: 指数代码
            start: 开始日期
            end: 结束日期

        Returns:
            指数历史DataFrame
        """
        pass

    @abstractmethod
    def get_index_hist(
        self,
        code: str,
        period: str = "daily",
        start: str | None = None,
        end: str | None = None,
    ) -> pd.DataFrame:
        """
        东财-指数通用历史行情

        Args:
            code: 指数代码
            period: 周期
            start: 开始日期
            end: 结束日期

        Returns:
            指数历史DataFrame
        """
        pass

    @abstractmethod
    def get_index_minute(
        self,
        code: str,
        period: str = "1",
        start: str | None = None,
        end: str | None = None,
    ) -> pd.DataFrame:
        """
        东财-指数分时行情

        Args:
            code: 指数代码
            period: 分钟周期
            start: 开始日期
            end: 结束日期

        Returns:
            指数分时DataFrame
        """
        pass

    # =========================================================================
    # P2 - 辅助分析功能接口 (57个)
    # =========================================================================

    # ----- 宏观经济数据 (22个) -----
    @abstractmethod
    def get_macro_leverage_ratio(self) -> pd.DataFrame:
        """中国宏观杠杆率"""
        pass

    @abstractmethod
    def get_enterprise_price_index(self) -> pd.DataFrame:
        """企业商品价格指数"""
        pass

    @abstractmethod
    def get_fdi_data(self) -> pd.DataFrame:
        """外商直接投资数据"""
        pass

    @abstractmethod
    def get_lpr_data(self) -> pd.DataFrame:
        """LPR品种数据"""
        pass

    @abstractmethod
    def get_urban_unemployment(self) -> pd.DataFrame:
        """城镇调查失业率"""
        pass

    @abstractmethod
    def get_social_financing(self) -> pd.DataFrame:
        """社会融资规模增量"""
        pass

    @abstractmethod
    def get_gdp_yearly(self) -> pd.DataFrame:
        """中国GDP年率"""
        pass

    @abstractmethod
    def get_gdp_quarterly(self) -> pd.DataFrame:
        """中国GDP季度数据"""
        pass

    @abstractmethod
    def get_cpi_yearly(self) -> pd.DataFrame:
        """中国CPI年率"""
        pass

    @abstractmethod
    def get_cpi_monthly(self) -> pd.DataFrame:
        """中国CPI月率"""
        pass

    @abstractmethod
    def get_ppi_yearly(self) -> pd.DataFrame:
        """中国PPI年率"""
        pass

    @abstractmethod
    def get_ppi_monthly(self) -> pd.DataFrame:
        """中国PPI月率"""
        pass

    @abstractmethod
    def get_exports_yearly(self) -> pd.DataFrame:
        """出口年率"""
        pass

    @abstractmethod
    def get_imports_yearly(self) -> pd.DataFrame:
        """进口年率"""
        pass

    @abstractmethod
    def get_trade_balance(self) -> pd.DataFrame:
        """贸易帐"""
        pass

    @abstractmethod
    def get_industrial_production(self) -> pd.DataFrame:
        """工业增加值增长"""
        pass

    @abstractmethod
    def get_pmi_official(self) -> pd.DataFrame:
        """官方制造业PMI"""
        pass

    @abstractmethod
    def get_pmi_caixin(self) -> pd.DataFrame:
        """财新制造业PMI"""
        pass

    @abstractmethod
    def get_services_pmi(self) -> pd.DataFrame:
        """财新服务业PMI"""
        pass

    @abstractmethod
    def get_non_manufacturing_pmi(self) -> pd.DataFrame:
        """官方非制造业PMI"""
        pass

    @abstractmethod
    def get_m2_yearly(self) -> pd.DataFrame:
        """M2货币供应年率"""
        pass

    @abstractmethod
    def get_new_loan(self) -> pd.DataFrame:
        """新增人民币贷款"""
        pass

    # ----- 利率数据 (8个) -----
    @abstractmethod
    def get_china_interest_rate(self) -> pd.DataFrame:
        """中国央行利率决议"""
        pass

    @abstractmethod
    def get_usa_interest_rate(self) -> pd.DataFrame:
        """美联储利率决议"""
        pass

    @abstractmethod
    def get_euro_interest_rate(self) -> pd.DataFrame:
        """欧洲央行利率决议"""
        pass

    @abstractmethod
    def get_japan_interest_rate(self) -> pd.DataFrame:
        """日本央行利率决议"""
        pass

    @abstractmethod
    def get_uk_interest_rate(self) -> pd.DataFrame:
        """英国央行利率决议"""
        pass

    @abstractmethod
    def get_shibor(self) -> pd.DataFrame:
        """SHIBOR利率"""
        pass

    @abstractmethod
    def get_shibor_lpr(self) -> pd.DataFrame:
        """SHIBOR-LPR"""
        pass

    @abstractmethod
    def get_hibor(self) -> pd.DataFrame:
        """人民币香港银行同业拆息"""
        pass

    # ----- 行业板块数据 (5个) -----
    @abstractmethod
    def get_industry_boards(self) -> pd.DataFrame:
        """行业板块列表"""
        pass

    @abstractmethod
    def get_industry_board_hist(
        self, code: str, period: str = "daily", start: str | None = None, end: str | None = None
    ) -> pd.DataFrame:
        """行业板块历史行情"""
        pass

    @abstractmethod
    def get_concept_boards(self) -> pd.DataFrame:
        """概念板块列表"""
        pass

    @abstractmethod
    def get_concept_board_hist(
        self, code: str, period: str = "daily", start: str | None = None, end: str | None = None
    ) -> pd.DataFrame:
        """概念板块历史行情"""
        pass

    @abstractmethod
    def get_sector_fund_flow(self, period: str = "今日") -> pd.DataFrame:
        """板块资金流向"""
        pass

    # ----- 债券数据 (7个) -----
    @abstractmethod
    def get_china_us_bond_yield(self) -> pd.DataFrame:
        """中美国债收益率"""
        pass

    @abstractmethod
    def get_bond_yield_curve(
        self,
        bond_type: str = "国债",
        period: str = "daily",
        start: str | None = None,
        end: str | None = None,
    ) -> pd.DataFrame:
        """收盘收益率曲线"""
        pass

    @abstractmethod
    def get_bond_spot_quote(self) -> pd.DataFrame:
        """现券市场做市报价"""
        pass

    @abstractmethod
    def get_convertible_bonds(self) -> pd.DataFrame:
        """可转债数据一览"""
        pass

    @abstractmethod
    def get_convertible_bond_detail(self, code: str) -> dict[str, Any]:
        """可转债详情"""
        pass

    @abstractmethod
    def get_bond_spot(self, code: str) -> pd.DataFrame:
        """沪深债券实时行情"""
        pass

    @abstractmethod
    def get_bond_hist(
        self, code: str, period: str = "daily", start: str | None = None, end: str | None = None
    ) -> pd.DataFrame:
        """沪深债券历史行情"""
        pass

    # ----- 估值指标 (5个) -----
    @abstractmethod
    def get_a_share_valuation(self) -> pd.DataFrame:
        """A股等权重与中位数PE/PB"""
        pass

    @abstractmethod
    def get_stock_valuation_lg(self, code: str) -> pd.DataFrame:
        """个股估值-乐咕乐股"""
        pass

    @abstractmethod
    def get_index_valuation(
        self, code: str, indicator: str = "pe"
    ) -> pd.DataFrame:
        """指数估值-乐咕乐股"""
        pass

    @abstractmethod
    def get_market_pe_lg(self, code: str) -> pd.DataFrame:
        """指数市盈率-乐咕乐股"""
        pass

    @abstractmethod
    def get_market_pb_lg(self, code: str) -> pd.DataFrame:
        """指数市净率-乐咕乐股"""
        pass

    # ----- 资金流向 (3个) -----
    @abstractmethod
    def get_market_fund_flow(self) -> pd.DataFrame:
        """大盘资金流向"""
        pass

    @abstractmethod
    def get_stock_fund_flow(self, code: str, market: str = "sh") -> pd.DataFrame:
        """个股资金流向"""
        pass

    @abstractmethod
    def get_north_fund_flow(self, market: str = "北向资金") -> pd.DataFrame:
        """沪深港通资金流向"""
        pass

    # ----- 其他 (2个) -----
    @abstractmethod
    def get_retail_sales_yearly(self) -> pd.DataFrame:
        """社会消费品零售总额"""
        pass

    @abstractmethod
    def get_fixed_asset_investment(self) -> pd.DataFrame:
        """固定资产投资"""
        pass

    # ----- 已有接口(保持兼容) -----
    @abstractmethod
    def get_fund_manager(self, fund_code: str) -> dict[str, Any]:
        """获取基金经理信息"""
        pass

    @abstractmethod
    def get_fund_rating(self, fund_code: str) -> int | None:
        """获取基金评级"""
        pass

    @abstractmethod
    def batch_get_fund_nav(
        self,
        fund_codes: list[str],
        start_date: date | None = None,
        end_date: date | None = None,
    ) -> dict[str, pd.DataFrame]:
        """批量获取基金净值数据"""
        pass

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(name={self._name!r})"

name property

name: str

数据源名称

__init__

__init__(name: str)

初始化数据源适配器

参数:

名称 类型 描述 默认
name str

数据源名称

必需
源代码位于: src/fund_cli/data/base.py
def __init__(self, name: str):
    """
    初始化数据源适配器

    Args:
        name: 数据源名称
    """
    self._name = name

is_available abstractmethod

is_available() -> bool

检查数据源是否可用

返回:

类型 描述
bool

数据源是否可用

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def is_available(self) -> bool:
    """
    检查数据源是否可用

    Returns:
        数据源是否可用
    """
    pass

get_fund_info abstractmethod

get_fund_info(fund_code: str) -> dict[str, Any]

获取基金基础信息

参数:

名称 类型 描述 默认
fund_code str

基金代码(6位数字)

必需

返回:

类型 描述
dict[str, Any]

基金基础信息字典

引发:

类型 描述
DataNotFoundError

基金不存在

DataSourceError

数据源错误

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_info(self, fund_code: str) -> dict[str, Any]:
    """
    获取基金基础信息

    Args:
        fund_code: 基金代码(6位数字)

    Returns:
        基金基础信息字典

    Raises:
        DataNotFoundError: 基金不存在
        DataSourceError: 数据源错误
    """
    pass

get_all_fund_names abstractmethod

get_all_fund_names() -> pd.DataFrame

获取所有基金名称列表

返回:

类型 描述
DataFrame

DataFrame包含:基金代码, 拼音缩写, 基金简称, 基金类型, 拼音全称

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_all_fund_names(self) -> pd.DataFrame:
    """
    获取所有基金名称列表

    Returns:
        DataFrame包含:基金代码, 拼音缩写, 基金简称, 基金类型, 拼音全称
    """
    pass

get_fund_info_ths abstractmethod

get_fund_info_ths(fund_code: str) -> dict[str, Any]

同花顺-基金基本信息

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需

返回:

类型 描述
dict[str, Any]

基金详细信息字典

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_info_ths(self, fund_code: str) -> dict[str, Any]:
    """
    同花顺-基金基本信息

    Args:
        fund_code: 基金代码

    Returns:
        基金详细信息字典
    """
    pass

get_index_fund_info abstractmethod

get_index_fund_info(
    category: str = "全部", indicator: str = "全部"
) -> pd.DataFrame

东方财富-指数型基金基本信息

参数:

名称 类型 描述 默认
category str

分类,可选"全部","沪深指数","行业主题","大盘指数"等

'全部'
indicator str

指标,可选"全部","被动指数型","增强指数型"

'全部'

返回:

类型 描述
DataFrame

指数型基金信息DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_index_fund_info(
    self, category: str = "全部", indicator: str = "全部"
) -> pd.DataFrame:
    """
    东方财富-指数型基金基本信息

    Args:
        category: 分类,可选"全部","沪深指数","行业主题","大盘指数"等
        indicator: 指标,可选"全部","被动指数型","增强指数型"

    Returns:
        指数型基金信息DataFrame
    """
    pass

get_fund_overview abstractmethod

get_fund_overview(fund_code: str) -> dict[str, Any]

天天基金-基金档案基本概况

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需

返回:

类型 描述
dict[str, Any]

基金概况字典

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_overview(self, fund_code: str) -> dict[str, Any]:
    """
    天天基金-基金档案基本概况

    Args:
        fund_code: 基金代码

    Returns:
        基金概况字典
    """
    pass

get_fund_purchase_status abstractmethod

get_fund_purchase_status() -> pd.DataFrame

东方财富-基金申购/赎回状态

返回:

类型 描述
DataFrame

DataFrame包含:基金代码, 基金简称, 申购状态, 赎回状态等

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_purchase_status(self) -> pd.DataFrame:
    """
    东方财富-基金申购/赎回状态

    Returns:
        DataFrame包含:基金代码, 基金简称, 申购状态, 赎回状态等
    """
    pass

get_fund_nav abstractmethod

get_fund_nav(
    fund_code: str,
    start_date: date | None = None,
    end_date: date | None = None,
) -> pd.DataFrame

获取基金净值数据

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需
start_date date | None

开始日期

None
end_date date | None

结束日期

None

返回:

类型 描述
DataFrame

净值数据DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_nav(
    self,
    fund_code: str,
    start_date: date | None = None,
    end_date: date | None = None,
) -> pd.DataFrame:
    """
    获取基金净值数据

    Args:
        fund_code: 基金代码
        start_date: 开始日期
        end_date: 结束日期

    Returns:
        净值数据DataFrame
    """
    pass

get_fund_daily_nav abstractmethod

get_fund_daily_nav() -> pd.DataFrame

东方财富-开放式基金每日净值(全部)

返回:

类型 描述
DataFrame

全部基金净值DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_daily_nav(self) -> pd.DataFrame:
    """
    东方财富-开放式基金每日净值(全部)

    Returns:
        全部基金净值DataFrame
    """
    pass

get_etf_spot abstractmethod

get_etf_spot() -> pd.DataFrame

东方财富-ETF实时行情(全部)

返回:

类型 描述
DataFrame

ETF实时行情DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_etf_spot(self) -> pd.DataFrame:
    """
    东方财富-ETF实时行情(全部)

    Returns:
        ETF实时行情DataFrame
    """
    pass

get_fund_category_spot abstractmethod

get_fund_category_spot(
    category: str = "", date: str | None = None
) -> pd.DataFrame

同花顺-基金实时行情(按类型)

参数:

名称 类型 描述 默认
category str

基金类型,如"股票型","债券型","混合型","ETF","LOF"等

''
date str | None

日期

None

返回:

类型 描述
DataFrame

基金行情DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_category_spot(
    self, category: str = "", date: str | None = None
) -> pd.DataFrame:
    """
    同花顺-基金实时行情(按类型)

    Args:
        category: 基金类型,如"股票型","债券型","混合型","ETF","LOF"等
        date: 日期

    Returns:
        基金行情DataFrame
    """
    pass

get_etf_spot_ths abstractmethod

get_etf_spot_ths(date: str | None = None) -> pd.DataFrame

同花顺-ETF实时行情

参数:

名称 类型 描述 默认
date str | None

日期

None

返回:

类型 描述
DataFrame

ETF行情DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_etf_spot_ths(self, date: str | None = None) -> pd.DataFrame:
    """
    同花顺-ETF实时行情

    Args:
        date: 日期

    Returns:
        ETF行情DataFrame
    """
    pass

get_lof_spot abstractmethod

get_lof_spot() -> pd.DataFrame

东方财富-LOF实时行情(全部)

返回:

类型 描述
DataFrame

LOF实时行情DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_lof_spot(self) -> pd.DataFrame:
    """
    东方财富-LOF实时行情(全部)

    Returns:
        LOF实时行情DataFrame
    """
    pass

get_etf_hist abstractmethod

get_etf_hist(
    fund_code: str,
    period: str = "daily",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame

东方财富-ETF历史行情

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需
period str

周期,"daily","weekly","monthly"

'daily'
start_date str | None

开始日期

None
end_date str | None

结束日期

None

返回:

类型 描述
DataFrame

ETF历史行情DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_etf_hist(
    self,
    fund_code: str,
    period: str = "daily",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame:
    """
    东方财富-ETF历史行情

    Args:
        fund_code: 基金代码
        period: 周期,"daily","weekly","monthly"
        start_date: 开始日期
        end_date: 结束日期

    Returns:
        ETF历史行情DataFrame
    """
    pass

get_lof_hist abstractmethod

get_lof_hist(
    fund_code: str,
    period: str = "daily",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame

东方财富-LOF历史行情

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需
period str

周期

'daily'
start_date str | None

开始日期

None
end_date str | None

结束日期

None

返回:

类型 描述
DataFrame

LOF历史行情DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_lof_hist(
    self,
    fund_code: str,
    period: str = "daily",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame:
    """
    东方财富-LOF历史行情

    Args:
        fund_code: 基金代码
        period: 周期
        start_date: 开始日期
        end_date: 结束日期

    Returns:
        LOF历史行情DataFrame
    """
    pass

get_etf_minute abstractmethod

get_etf_minute(
    fund_code: str,
    period: str = "1",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame

东方财富-ETF分时行情

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需
period str

分钟周期,"1","5","15","30","60"

'1'
start_date str | None

开始日期

None
end_date str | None

结束日期

None

返回:

类型 描述
DataFrame

ETF分时行情DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_etf_minute(
    self,
    fund_code: str,
    period: str = "1",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame:
    """
    东方财富-ETF分时行情

    Args:
        fund_code: 基金代码
        period: 分钟周期,"1","5","15","30","60"
        start_date: 开始日期
        end_date: 结束日期

    Returns:
        ETF分时行情DataFrame
    """
    pass

get_lof_minute abstractmethod

get_lof_minute(
    fund_code: str,
    period: str = "1",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame

东方财富-LOF分时行情

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需
period str

分钟周期

'1'
start_date str | None

开始日期

None
end_date str | None

结束日期

None

返回:

类型 描述
DataFrame

LOF分时行情DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_lof_minute(
    self,
    fund_code: str,
    period: str = "1",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame:
    """
    东方财富-LOF分时行情

    Args:
        fund_code: 基金代码
        period: 分钟周期
        start_date: 开始日期
        end_date: 结束日期

    Returns:
        LOF分时行情DataFrame
    """
    pass

get_fund_holdings abstractmethod

get_fund_holdings(
    fund_code: str, report_date: date | None = None
) -> pd.DataFrame

获取基金持仓数据

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需
report_date date | None

报告日期

None

返回:

类型 描述
DataFrame

持仓数据DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_holdings(
    self,
    fund_code: str,
    report_date: date | None = None,
) -> pd.DataFrame:
    """
    获取基金持仓数据

    Args:
        fund_code: 基金代码
        report_date: 报告日期

    Returns:
        持仓数据DataFrame
    """
    pass

get_fund_bond_holdings abstractmethod

get_fund_bond_holdings(
    fund_code: str, year: int | None = None
) -> pd.DataFrame

天天基金-基金债券持仓

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需
year int | None

年份

None

返回:

类型 描述
DataFrame

债券持仓DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_bond_holdings(
    self, fund_code: str, year: int | None = None
) -> pd.DataFrame:
    """
    天天基金-基金债券持仓

    Args:
        fund_code: 基金代码
        year: 年份

    Returns:
        债券持仓DataFrame
    """
    pass

get_fund_industry_allocation abstractmethod

get_fund_industry_allocation(
    fund_code: str, year: int | None = None
) -> pd.DataFrame

天天基金-行业配置

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需
year int | None

年份

None

返回:

类型 描述
DataFrame

行业配置DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_industry_allocation(
    self, fund_code: str, year: int | None = None
) -> pd.DataFrame:
    """
    天天基金-行业配置

    Args:
        fund_code: 基金代码
        year: 年份

    Returns:
        行业配置DataFrame
    """
    pass

get_fund_portfolio_change abstractmethod

get_fund_portfolio_change(
    fund_code: str,
    indicator: str = "累计买入",
    year: int | None = None,
) -> pd.DataFrame

天天基金-重大变动(累计买入/卖出)

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需
indicator str

"累计买入"或"累计卖出"

'累计买入'
year int | None

年份

None

返回:

类型 描述
DataFrame

持仓变动DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_portfolio_change(
    self, fund_code: str, indicator: str = "累计买入", year: int | None = None
) -> pd.DataFrame:
    """
    天天基金-重大变动(累计买入/卖出)

    Args:
        fund_code: 基金代码
        indicator: "累计买入"或"累计卖出"
        year: 年份

    Returns:
        持仓变动DataFrame
    """
    pass

get_all_fund_managers abstractmethod

get_all_fund_managers() -> pd.DataFrame

天天基金-基金经理大全

返回:

类型 描述
DataFrame

基金经理信息DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_all_fund_managers(self) -> pd.DataFrame:
    """
    天天基金-基金经理大全

    Returns:
        基金经理信息DataFrame
    """
    pass

search_funds abstractmethod

search_funds(
    fund_type: str | None = None,
    company: str | None = None,
    min_scale: float | None = None,
    max_scale: float | None = None,
    keyword: str | None = None,
    limit: int = 100,
) -> pd.DataFrame

搜索/筛选基金

参数:

名称 类型 描述 默认
fund_type str | None

基金类型

None
company str | None

基金公司

None
min_scale float | None

最小规模(亿元)

None
max_scale float | None

最大规模(亿元)

None
keyword str | None

关键词搜索

None
limit int

返回数量限制

100

返回:

类型 描述
DataFrame

基金列表DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def search_funds(
    self,
    fund_type: str | None = None,
    company: str | None = None,
    min_scale: float | None = None,
    max_scale: float | None = None,
    keyword: str | None = None,
    limit: int = 100,
) -> pd.DataFrame:
    """
    搜索/筛选基金

    Args:
        fund_type: 基金类型
        company: 基金公司
        min_scale: 最小规模(亿元)
        max_scale: 最大规模(亿元)
        keyword: 关键词搜索
        limit: 返回数量限制

    Returns:
        基金列表DataFrame
    """
    pass

get_fund_list abstractmethod

get_fund_list(fund_type: str | None = None) -> pd.DataFrame

获取基金列表

参数:

名称 类型 描述 默认
fund_type str | None

基金类型筛选

None

返回:

类型 描述
DataFrame

基金列表DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_list(self, fund_type: str | None = None) -> pd.DataFrame:
    """
    获取基金列表

    Args:
        fund_type: 基金类型筛选

    Returns:
        基金列表DataFrame
    """
    pass

get_benchmark_nav abstractmethod

get_benchmark_nav(
    benchmark_code: str,
    start_date: date | None = None,
    end_date: date | None = None,
) -> pd.DataFrame

获取基准指数净值数据

参数:

名称 类型 描述 默认
benchmark_code str

基准指数代码

必需
start_date date | None

开始日期

None
end_date date | None

结束日期

None

返回:

类型 描述
DataFrame

基准净值数据DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_benchmark_nav(
    self,
    benchmark_code: str,
    start_date: date | None = None,
    end_date: date | None = None,
) -> pd.DataFrame:
    """
    获取基准指数净值数据

    Args:
        benchmark_code: 基准指数代码
        start_date: 开始日期
        end_date: 结束日期

    Returns:
        基准净值数据DataFrame
    """
    pass

get_fund_fee abstractmethod

get_fund_fee(fund_code: str) -> dict[str, Any]

获取基金费率信息

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需

返回:

类型 描述
dict[str, Any]

费率信息字典

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_fee(self, fund_code: str) -> dict[str, Any]:
    """
    获取基金费率信息

    Args:
        fund_code: 基金代码

    Returns:
        费率信息字典
    """
    pass

get_fund_company_aum abstractmethod

get_fund_company_aum() -> pd.DataFrame

东方财富-基金公司管理规模排名

返回:

类型 描述
DataFrame

基金公司规模排名DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_company_aum(self) -> pd.DataFrame:
    """
    东方财富-基金公司管理规模排名

    Returns:
        基金公司规模排名DataFrame
    """
    pass

get_fund_aum_trend abstractmethod

get_fund_aum_trend() -> pd.DataFrame

东方财富-基金市场管理规模走势

返回:

类型 描述
DataFrame

规模走势DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_aum_trend(self) -> pd.DataFrame:
    """
    东方财富-基金市场管理规模走势

    Returns:
        规模走势DataFrame
    """
    pass

get_fund_company_aum_history abstractmethod

get_fund_company_aum_history(
    year: int | None = None,
) -> pd.DataFrame

东方财富-基金公司历年管理规模排行

参数:

名称 类型 描述 默认
year int | None

年份

None

返回:

类型 描述
DataFrame

历年规模排行DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_company_aum_history(self, year: int | None = None) -> pd.DataFrame:
    """
    东方财富-基金公司历年管理规模排行

    Args:
        year: 年份

    Returns:
        历年规模排行DataFrame
    """
    pass

get_fund_scale_change abstractmethod

get_fund_scale_change() -> pd.DataFrame

天天基金-规模变动(全市场汇总)

返回:

类型 描述
DataFrame

规模变动DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_scale_change(self) -> pd.DataFrame:
    """
    天天基金-规模变动(全市场汇总)

    Returns:
        规模变动DataFrame
    """
    pass

get_fund_holder_structure abstractmethod

get_fund_holder_structure() -> pd.DataFrame

天天基金-持有人结构(全市场汇总)

返回:

类型 描述
DataFrame

持有人结构DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_holder_structure(self) -> pd.DataFrame:
    """
    天天基金-持有人结构(全市场汇总)

    Returns:
        持有人结构DataFrame
    """
    pass

get_fund_ratings abstractmethod

get_fund_ratings() -> pd.DataFrame

天天基金-基金评级总汇

返回:

类型 描述
DataFrame

基金评级DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_ratings(self) -> pd.DataFrame:
    """
    天天基金-基金评级总汇

    Returns:
        基金评级DataFrame
    """
    pass

get_fund_rating_sh abstractmethod

get_fund_rating_sh(date: str | None = None) -> pd.DataFrame

天天基金-上海证券评级

参数:

名称 类型 描述 默认
date str | None

日期(YYYYMMDD)

None

返回:

类型 描述
DataFrame

上海证券评级DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_rating_sh(self, date: str | None = None) -> pd.DataFrame:
    """
    天天基金-上海证券评级

    Args:
        date: 日期(YYYYMMDD)

    Returns:
        上海证券评级DataFrame
    """
    pass

get_fund_rating_zs abstractmethod

get_fund_rating_zs(date: str | None = None) -> pd.DataFrame

天天基金-招商证券评级

参数:

名称 类型 描述 默认
date str | None

日期(YYYYMMDD)

None

返回:

类型 描述
DataFrame

招商证券评级DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_rating_zs(self, date: str | None = None) -> pd.DataFrame:
    """
    天天基金-招商证券评级

    Args:
        date: 日期(YYYYMMDD)

    Returns:
        招商证券评级DataFrame
    """
    pass

get_fund_rating_ja abstractmethod

get_fund_rating_ja(date: str | None = None) -> pd.DataFrame

天天基金-济安金信评级

参数:

名称 类型 描述 默认
date str | None

日期(YYYYMMDD)

None

返回:

类型 描述
DataFrame

济安金信评级DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_rating_ja(self, date: str | None = None) -> pd.DataFrame:
    """
    天天基金-济安金信评级

    Args:
        date: 日期(YYYYMMDD)

    Returns:
        济安金信评级DataFrame
    """
    pass

get_fund_dividends abstractmethod

get_fund_dividends(
    year: int | None = None,
    fund_type: str = "",
    page: int = -1,
) -> pd.DataFrame

天天基金-基金分红

参数:

名称 类型 描述 默认
year int | None

年份

None
fund_type str

基金类型

''
page int

页码,-1为全部

-1

返回:

类型 描述
DataFrame

分红数据DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_dividends(
    self,
    year: int | None = None,
    fund_type: str = "",
    page: int = -1,
) -> pd.DataFrame:
    """
    天天基金-基金分红

    Args:
        year: 年份
        fund_type: 基金类型
        page: 页码,-1为全部

    Returns:
        分红数据DataFrame
    """
    pass

get_fund_splits abstractmethod

get_fund_splits(
    year: int | None = None,
    fund_type: str = "",
    page: int = -1,
) -> pd.DataFrame

天天基金-基金拆分

参数:

名称 类型 描述 默认
year int | None

年份

None
fund_type str

基金类型

''
page int

页码

-1

返回:

类型 描述
DataFrame

拆分数据DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_splits(
    self,
    year: int | None = None,
    fund_type: str = "",
    page: int = -1,
) -> pd.DataFrame:
    """
    天天基金-基金拆分

    Args:
        year: 年份
        fund_type: 基金类型
        page: 页码

    Returns:
        拆分数据DataFrame
    """
    pass

get_fund_dividend_rank abstractmethod

get_fund_dividend_rank() -> pd.DataFrame

天天基金-基金累计分红排行

返回:

类型 描述
DataFrame

分红排行DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_dividend_rank(self) -> pd.DataFrame:
    """
    天天基金-基金累计分红排行

    Returns:
        分红排行DataFrame
    """
    pass

get_fund_rank_by_type abstractmethod

get_fund_rank_by_type(
    fund_type: str = "全部",
) -> pd.DataFrame

东方财富-开放式基金排行

参数:

名称 类型 描述 默认
fund_type str

基金类型,"全部","股票型","混合型","债券型"等

'全部'

返回:

类型 描述
DataFrame

基金排行DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_rank_by_type(self, fund_type: str = "全部") -> pd.DataFrame:
    """
    东方财富-开放式基金排行

    Args:
        fund_type: 基金类型,"全部","股票型","混合型","债券型"等

    Returns:
        基金排行DataFrame
    """
    pass

get_exchange_fund_rank abstractmethod

get_exchange_fund_rank() -> pd.DataFrame

东方财富-场内交易基金排行

返回:

类型 描述
DataFrame

场内基金排行DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_exchange_fund_rank(self) -> pd.DataFrame:
    """
    东方财富-场内交易基金排行

    Returns:
        场内基金排行DataFrame
    """
    pass

get_money_fund_rank abstractmethod

get_money_fund_rank() -> pd.DataFrame

东方财富-货币型基金排行

返回:

类型 描述
DataFrame

货币基金排行DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_money_fund_rank(self) -> pd.DataFrame:
    """
    东方财富-货币型基金排行

    Returns:
        货币基金排行DataFrame
    """
    pass

get_lcx_fund_rank abstractmethod

get_lcx_fund_rank() -> pd.DataFrame

东方财富-理财基金排行

返回:

类型 描述
DataFrame

理财基金排行DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_lcx_fund_rank(self) -> pd.DataFrame:
    """
    东方财富-理财基金排行

    Returns:
        理财基金排行DataFrame
    """
    pass

get_hk_fund_rank abstractmethod

get_hk_fund_rank() -> pd.DataFrame

东方财富-香港基金排行

返回:

类型 描述
DataFrame

香港基金排行DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_hk_fund_rank(self) -> pd.DataFrame:
    """
    东方财富-香港基金排行

    Returns:
        香港基金排行DataFrame
    """
    pass

get_fund_achievement abstractmethod

get_fund_achievement(fund_code: str) -> pd.DataFrame

雪球-基金业绩(年度+阶段)

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需

返回:

类型 描述
DataFrame

业绩数据DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_achievement(self, fund_code: str) -> pd.DataFrame:
    """
    雪球-基金业绩(年度+阶段)

    Args:
        fund_code: 基金代码

    Returns:
        业绩数据DataFrame
    """
    pass

get_fund_risk_analysis abstractmethod

get_fund_risk_analysis(fund_code: str) -> pd.DataFrame

雪球-基金数据分析(夏普/回撤等)

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需

返回:

类型 描述
DataFrame

风险分析DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_risk_analysis(self, fund_code: str) -> pd.DataFrame:
    """
    雪球-基金数据分析(夏普/回撤等)

    Args:
        fund_code: 基金代码

    Returns:
        风险分析DataFrame
    """
    pass

get_fund_profit_probability abstractmethod

get_fund_profit_probability(fund_code: str) -> pd.DataFrame

雪球-基金盈利概率

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需

返回:

类型 描述
DataFrame

盈利概率DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_profit_probability(self, fund_code: str) -> pd.DataFrame:
    """
    雪球-基金盈利概率

    Args:
        fund_code: 基金代码

    Returns:
        盈利概率DataFrame
    """
    pass

get_fund_asset_allocation abstractmethod

get_fund_asset_allocation(
    fund_code: str, date: str | None = None
) -> pd.DataFrame

雪球-基金资产配置

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需
date str | None

财报日期(YYYYMMDD)

None

返回:

类型 描述
DataFrame

资产配置DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_asset_allocation(
    self, fund_code: str, date: str | None = None
) -> pd.DataFrame:
    """
    雪球-基金资产配置

    Args:
        fund_code: 基金代码
        date: 财报日期(YYYYMMDD)

    Returns:
        资产配置DataFrame
    """
    pass

get_index_spot_em abstractmethod

get_index_spot_em(category: str = '沪深重要指数') -> pd.DataFrame

东财-沪深京指数实时行情

参数:

名称 类型 描述 默认
category str

指数分类

'沪深重要指数'

返回:

类型 描述
DataFrame

指数行情DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_index_spot_em(self, category: str = "沪深重要指数") -> pd.DataFrame:
    """
    东财-沪深京指数实时行情

    Args:
        category: 指数分类

    Returns:
        指数行情DataFrame
    """
    pass

get_index_spot_sina abstractmethod

get_index_spot_sina() -> pd.DataFrame

新浪-中国股票指数实时行情

返回:

类型 描述
DataFrame

指数行情DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_index_spot_sina(self) -> pd.DataFrame:
    """
    新浪-中国股票指数实时行情

    Returns:
        指数行情DataFrame
    """
    pass

get_index_daily_tx abstractmethod

get_index_daily_tx(
    code: str,
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame

腾讯-指数历史行情

参数:

名称 类型 描述 默认
code str

指数代码

必需
start str | None

开始日期

None
end str | None

结束日期

None

返回:

类型 描述
DataFrame

指数历史DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_index_daily_tx(
    self, code: str, start: str | None = None, end: str | None = None
) -> pd.DataFrame:
    """
    腾讯-指数历史行情

    Args:
        code: 指数代码
        start: 开始日期
        end: 结束日期

    Returns:
        指数历史DataFrame
    """
    pass

get_index_daily_em abstractmethod

get_index_daily_em(
    code: str,
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame

东财-指数历史行情

参数:

名称 类型 描述 默认
code str

指数代码

必需
start str | None

开始日期

None
end str | None

结束日期

None

返回:

类型 描述
DataFrame

指数历史DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_index_daily_em(
    self, code: str, start: str | None = None, end: str | None = None
) -> pd.DataFrame:
    """
    东财-指数历史行情

    Args:
        code: 指数代码
        start: 开始日期
        end: 结束日期

    Returns:
        指数历史DataFrame
    """
    pass

get_index_hist abstractmethod

get_index_hist(
    code: str,
    period: str = "daily",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame

东财-指数通用历史行情

参数:

名称 类型 描述 默认
code str

指数代码

必需
period str

周期

'daily'
start str | None

开始日期

None
end str | None

结束日期

None

返回:

类型 描述
DataFrame

指数历史DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_index_hist(
    self,
    code: str,
    period: str = "daily",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame:
    """
    东财-指数通用历史行情

    Args:
        code: 指数代码
        period: 周期
        start: 开始日期
        end: 结束日期

    Returns:
        指数历史DataFrame
    """
    pass

get_index_minute abstractmethod

get_index_minute(
    code: str,
    period: str = "1",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame

东财-指数分时行情

参数:

名称 类型 描述 默认
code str

指数代码

必需
period str

分钟周期

'1'
start str | None

开始日期

None
end str | None

结束日期

None

返回:

类型 描述
DataFrame

指数分时DataFrame

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_index_minute(
    self,
    code: str,
    period: str = "1",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame:
    """
    东财-指数分时行情

    Args:
        code: 指数代码
        period: 分钟周期
        start: 开始日期
        end: 结束日期

    Returns:
        指数分时DataFrame
    """
    pass

get_macro_leverage_ratio abstractmethod

get_macro_leverage_ratio() -> pd.DataFrame

中国宏观杠杆率

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_macro_leverage_ratio(self) -> pd.DataFrame:
    """中国宏观杠杆率"""
    pass

get_enterprise_price_index abstractmethod

get_enterprise_price_index() -> pd.DataFrame

企业商品价格指数

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_enterprise_price_index(self) -> pd.DataFrame:
    """企业商品价格指数"""
    pass

get_fdi_data abstractmethod

get_fdi_data() -> pd.DataFrame

外商直接投资数据

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fdi_data(self) -> pd.DataFrame:
    """外商直接投资数据"""
    pass

get_lpr_data abstractmethod

get_lpr_data() -> pd.DataFrame

LPR品种数据

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_lpr_data(self) -> pd.DataFrame:
    """LPR品种数据"""
    pass

get_urban_unemployment abstractmethod

get_urban_unemployment() -> pd.DataFrame

城镇调查失业率

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_urban_unemployment(self) -> pd.DataFrame:
    """城镇调查失业率"""
    pass

get_social_financing abstractmethod

get_social_financing() -> pd.DataFrame

社会融资规模增量

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_social_financing(self) -> pd.DataFrame:
    """社会融资规模增量"""
    pass

get_gdp_yearly abstractmethod

get_gdp_yearly() -> pd.DataFrame

中国GDP年率

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_gdp_yearly(self) -> pd.DataFrame:
    """中国GDP年率"""
    pass

get_gdp_quarterly abstractmethod

get_gdp_quarterly() -> pd.DataFrame

中国GDP季度数据

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_gdp_quarterly(self) -> pd.DataFrame:
    """中国GDP季度数据"""
    pass

get_cpi_yearly abstractmethod

get_cpi_yearly() -> pd.DataFrame

中国CPI年率

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_cpi_yearly(self) -> pd.DataFrame:
    """中国CPI年率"""
    pass

get_cpi_monthly abstractmethod

get_cpi_monthly() -> pd.DataFrame

中国CPI月率

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_cpi_monthly(self) -> pd.DataFrame:
    """中国CPI月率"""
    pass

get_ppi_yearly abstractmethod

get_ppi_yearly() -> pd.DataFrame

中国PPI年率

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_ppi_yearly(self) -> pd.DataFrame:
    """中国PPI年率"""
    pass

get_ppi_monthly abstractmethod

get_ppi_monthly() -> pd.DataFrame

中国PPI月率

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_ppi_monthly(self) -> pd.DataFrame:
    """中国PPI月率"""
    pass

get_exports_yearly abstractmethod

get_exports_yearly() -> pd.DataFrame

出口年率

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_exports_yearly(self) -> pd.DataFrame:
    """出口年率"""
    pass

get_imports_yearly abstractmethod

get_imports_yearly() -> pd.DataFrame

进口年率

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_imports_yearly(self) -> pd.DataFrame:
    """进口年率"""
    pass

get_trade_balance abstractmethod

get_trade_balance() -> pd.DataFrame

贸易帐

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_trade_balance(self) -> pd.DataFrame:
    """贸易帐"""
    pass

get_industrial_production abstractmethod

get_industrial_production() -> pd.DataFrame

工业增加值增长

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_industrial_production(self) -> pd.DataFrame:
    """工业增加值增长"""
    pass

get_pmi_official abstractmethod

get_pmi_official() -> pd.DataFrame

官方制造业PMI

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_pmi_official(self) -> pd.DataFrame:
    """官方制造业PMI"""
    pass

get_pmi_caixin abstractmethod

get_pmi_caixin() -> pd.DataFrame

财新制造业PMI

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_pmi_caixin(self) -> pd.DataFrame:
    """财新制造业PMI"""
    pass

get_services_pmi abstractmethod

get_services_pmi() -> pd.DataFrame

财新服务业PMI

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_services_pmi(self) -> pd.DataFrame:
    """财新服务业PMI"""
    pass

get_non_manufacturing_pmi abstractmethod

get_non_manufacturing_pmi() -> pd.DataFrame

官方非制造业PMI

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_non_manufacturing_pmi(self) -> pd.DataFrame:
    """官方非制造业PMI"""
    pass

get_m2_yearly abstractmethod

get_m2_yearly() -> pd.DataFrame

M2货币供应年率

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_m2_yearly(self) -> pd.DataFrame:
    """M2货币供应年率"""
    pass

get_new_loan abstractmethod

get_new_loan() -> pd.DataFrame

新增人民币贷款

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_new_loan(self) -> pd.DataFrame:
    """新增人民币贷款"""
    pass

get_china_interest_rate abstractmethod

get_china_interest_rate() -> pd.DataFrame

中国央行利率决议

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_china_interest_rate(self) -> pd.DataFrame:
    """中国央行利率决议"""
    pass

get_usa_interest_rate abstractmethod

get_usa_interest_rate() -> pd.DataFrame

美联储利率决议

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_usa_interest_rate(self) -> pd.DataFrame:
    """美联储利率决议"""
    pass

get_euro_interest_rate abstractmethod

get_euro_interest_rate() -> pd.DataFrame

欧洲央行利率决议

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_euro_interest_rate(self) -> pd.DataFrame:
    """欧洲央行利率决议"""
    pass

get_japan_interest_rate abstractmethod

get_japan_interest_rate() -> pd.DataFrame

日本央行利率决议

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_japan_interest_rate(self) -> pd.DataFrame:
    """日本央行利率决议"""
    pass

get_uk_interest_rate abstractmethod

get_uk_interest_rate() -> pd.DataFrame

英国央行利率决议

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_uk_interest_rate(self) -> pd.DataFrame:
    """英国央行利率决议"""
    pass

get_shibor abstractmethod

get_shibor() -> pd.DataFrame

SHIBOR利率

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_shibor(self) -> pd.DataFrame:
    """SHIBOR利率"""
    pass

get_shibor_lpr abstractmethod

get_shibor_lpr() -> pd.DataFrame

SHIBOR-LPR

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_shibor_lpr(self) -> pd.DataFrame:
    """SHIBOR-LPR"""
    pass

get_hibor abstractmethod

get_hibor() -> pd.DataFrame

人民币香港银行同业拆息

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_hibor(self) -> pd.DataFrame:
    """人民币香港银行同业拆息"""
    pass

get_industry_boards abstractmethod

get_industry_boards() -> pd.DataFrame

行业板块列表

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_industry_boards(self) -> pd.DataFrame:
    """行业板块列表"""
    pass

get_industry_board_hist abstractmethod

get_industry_board_hist(
    code: str,
    period: str = "daily",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame

行业板块历史行情

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_industry_board_hist(
    self, code: str, period: str = "daily", start: str | None = None, end: str | None = None
) -> pd.DataFrame:
    """行业板块历史行情"""
    pass

get_concept_boards abstractmethod

get_concept_boards() -> pd.DataFrame

概念板块列表

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_concept_boards(self) -> pd.DataFrame:
    """概念板块列表"""
    pass

get_concept_board_hist abstractmethod

get_concept_board_hist(
    code: str,
    period: str = "daily",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame

概念板块历史行情

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_concept_board_hist(
    self, code: str, period: str = "daily", start: str | None = None, end: str | None = None
) -> pd.DataFrame:
    """概念板块历史行情"""
    pass

get_sector_fund_flow abstractmethod

get_sector_fund_flow(period: str = '今日') -> pd.DataFrame

板块资金流向

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_sector_fund_flow(self, period: str = "今日") -> pd.DataFrame:
    """板块资金流向"""
    pass

get_china_us_bond_yield abstractmethod

get_china_us_bond_yield() -> pd.DataFrame

中美国债收益率

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_china_us_bond_yield(self) -> pd.DataFrame:
    """中美国债收益率"""
    pass

get_bond_yield_curve abstractmethod

get_bond_yield_curve(
    bond_type: str = "国债",
    period: str = "daily",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame

收盘收益率曲线

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_bond_yield_curve(
    self,
    bond_type: str = "国债",
    period: str = "daily",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame:
    """收盘收益率曲线"""
    pass

get_bond_spot_quote abstractmethod

get_bond_spot_quote() -> pd.DataFrame

现券市场做市报价

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_bond_spot_quote(self) -> pd.DataFrame:
    """现券市场做市报价"""
    pass

get_convertible_bonds abstractmethod

get_convertible_bonds() -> pd.DataFrame

可转债数据一览

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_convertible_bonds(self) -> pd.DataFrame:
    """可转债数据一览"""
    pass

get_convertible_bond_detail abstractmethod

get_convertible_bond_detail(code: str) -> dict[str, Any]

可转债详情

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_convertible_bond_detail(self, code: str) -> dict[str, Any]:
    """可转债详情"""
    pass

get_bond_spot abstractmethod

get_bond_spot(code: str) -> pd.DataFrame

沪深债券实时行情

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_bond_spot(self, code: str) -> pd.DataFrame:
    """沪深债券实时行情"""
    pass

get_bond_hist abstractmethod

get_bond_hist(
    code: str,
    period: str = "daily",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame

沪深债券历史行情

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_bond_hist(
    self, code: str, period: str = "daily", start: str | None = None, end: str | None = None
) -> pd.DataFrame:
    """沪深债券历史行情"""
    pass

get_a_share_valuation abstractmethod

get_a_share_valuation() -> pd.DataFrame

A股等权重与中位数PE/PB

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_a_share_valuation(self) -> pd.DataFrame:
    """A股等权重与中位数PE/PB"""
    pass

get_stock_valuation_lg abstractmethod

get_stock_valuation_lg(code: str) -> pd.DataFrame

个股估值-乐咕乐股

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_stock_valuation_lg(self, code: str) -> pd.DataFrame:
    """个股估值-乐咕乐股"""
    pass

get_index_valuation abstractmethod

get_index_valuation(
    code: str, indicator: str = "pe"
) -> pd.DataFrame

指数估值-乐咕乐股

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_index_valuation(
    self, code: str, indicator: str = "pe"
) -> pd.DataFrame:
    """指数估值-乐咕乐股"""
    pass

get_market_pe_lg abstractmethod

get_market_pe_lg(code: str) -> pd.DataFrame

指数市盈率-乐咕乐股

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_market_pe_lg(self, code: str) -> pd.DataFrame:
    """指数市盈率-乐咕乐股"""
    pass

get_market_pb_lg abstractmethod

get_market_pb_lg(code: str) -> pd.DataFrame

指数市净率-乐咕乐股

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_market_pb_lg(self, code: str) -> pd.DataFrame:
    """指数市净率-乐咕乐股"""
    pass

get_market_fund_flow abstractmethod

get_market_fund_flow() -> pd.DataFrame

大盘资金流向

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_market_fund_flow(self) -> pd.DataFrame:
    """大盘资金流向"""
    pass

get_stock_fund_flow abstractmethod

get_stock_fund_flow(
    code: str, market: str = "sh"
) -> pd.DataFrame

个股资金流向

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_stock_fund_flow(self, code: str, market: str = "sh") -> pd.DataFrame:
    """个股资金流向"""
    pass

get_north_fund_flow abstractmethod

get_north_fund_flow(market: str = '北向资金') -> pd.DataFrame

沪深港通资金流向

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_north_fund_flow(self, market: str = "北向资金") -> pd.DataFrame:
    """沪深港通资金流向"""
    pass

get_retail_sales_yearly abstractmethod

get_retail_sales_yearly() -> pd.DataFrame

社会消费品零售总额

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_retail_sales_yearly(self) -> pd.DataFrame:
    """社会消费品零售总额"""
    pass

get_fixed_asset_investment abstractmethod

get_fixed_asset_investment() -> pd.DataFrame

固定资产投资

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fixed_asset_investment(self) -> pd.DataFrame:
    """固定资产投资"""
    pass

get_fund_manager abstractmethod

get_fund_manager(fund_code: str) -> dict[str, Any]

获取基金经理信息

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_manager(self, fund_code: str) -> dict[str, Any]:
    """获取基金经理信息"""
    pass

get_fund_rating abstractmethod

get_fund_rating(fund_code: str) -> int | None

获取基金评级

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def get_fund_rating(self, fund_code: str) -> int | None:
    """获取基金评级"""
    pass

batch_get_fund_nav abstractmethod

batch_get_fund_nav(
    fund_codes: list[str],
    start_date: date | None = None,
    end_date: date | None = None,
) -> dict[str, pd.DataFrame]

批量获取基金净值数据

源代码位于: src/fund_cli/data/base.py
@abstractmethod
def batch_get_fund_nav(
    self,
    fund_codes: list[str],
    start_date: date | None = None,
    end_date: date | None = None,
) -> dict[str, pd.DataFrame]:
    """批量获取基金净值数据"""
    pass

AKShareAdapter

Bases: DataSourceAdapter


              flowchart TD
              fund_cli.data.adapters.akshare_adapter.AKShareAdapter[AKShareAdapter]
              fund_cli.data.base.DataSourceAdapter[DataSourceAdapter]

                              fund_cli.data.base.DataSourceAdapter --> fund_cli.data.adapters.akshare_adapter.AKShareAdapter
                


              click fund_cli.data.adapters.akshare_adapter.AKShareAdapter href "" "fund_cli.data.adapters.akshare_adapter.AKShareAdapter"
              click fund_cli.data.base.DataSourceAdapter href "" "fund_cli.data.base.DataSourceAdapter"
            

AKShare 数据源适配器

使用 AKShare 开源库获取基金数据,特点: - 免费使用,无需Token - 数据覆盖全面 - 支持实时数据

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
  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
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
class AKShareAdapter(DataSourceAdapter):
    """
    AKShare 数据源适配器

    使用 AKShare 开源库获取基金数据,特点:
    - 免费使用,无需Token
    - 数据覆盖全面
    - 支持实时数据
    """

    def __init__(self, cache: DataCache | None = None):
        """
        初始化 AKShare 适配器

        Args:
            cache: 缓存管理器,可选
        """
        super().__init__("akshare")
        self._cache = cache
        self._ak = None

    def _get_akshare(self):
        """延迟加载 AKShare"""
        if self._ak is None:
            try:
                import akshare as ak

                self._ak = ak
            except ImportError as e:
                raise DataSourceError("AKShare 未安装,请运行: pip install akshare") from e
        return self._ak

    def is_available(self) -> bool:
        """检查 AKShare 是否可用"""
        try:
            self._get_akshare()
            return True
        except Exception:
            return False

    def get_fund_info(self, fund_code: str) -> dict[str, Any]:
        """
        获取基金基础信息

        Args:
            fund_code: 基金代码

        Returns:
            基金信息字典
        """
        # 检查缓存
        if self._cache:
            cached = self._cache.get_fund_info(fund_code)
            if cached:
                return cached

        ak = self._get_akshare()

        try:
            # 获取基金基本信息
            df = ak.fund_individual_basic_info_xq(symbol=fund_code)

            if df.empty:
                raise DataNotFoundError(f"基金 {fund_code} 不存在")

            # 解析数据
            info_dict = dict(zip(df["item"], df["value"], strict=False))

            result = {
                "code": fund_code,
                "name": info_dict.get("基金简称", info_dict.get("基金全称", "")),
                "type": info_dict.get("基金类型", "未知"),
                "establish_date": self._parse_date(info_dict.get("成立日期")),
                "manager": info_dict.get("基金经理", ""),
                "company": info_dict.get("基金管理人", ""),
                "scale": self._parse_scale(info_dict.get("基金规模")),
            }

            # 缓存结果
            if self._cache:
                self._cache.set_fund_info(fund_code, result)

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金信息失败: {e}") from e

    def get_fund_nav(
        self,
        fund_code: str,
        start_date: date | None = None,
        end_date: date | None = None,
    ) -> pd.DataFrame:
        """
        获取基金净值数据

        Args:
            fund_code: 基金代码
            start_date: 开始日期
            end_date: 结束日期

        Returns:
            净值数据 DataFrame
        """
        # 格式化日期
        start_str = start_date.strftime("%Y%m%d") if start_date else "19900101"
        end_str = end_date.strftime("%Y%m%d") if end_date else datetime.now().strftime("%Y%m%d")

        # 检查缓存
        if self._cache:
            cached = self._cache.get_fund_nav(fund_code, start_str, end_str)
            if cached is not None:
                return cached

        ak = self._get_akshare()

        try:
            # 获取开放式基金净值数据
            df = ak.fund_open_fund_info_em(fund=fund_code, indicator="单位净值走势")

            if df.empty:
                raise DataNotFoundError(f"基金 {fund_code} 净值数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "净值日期": "nav_date",
                    "单位净值": "unit_nav",
                    "日增长率": "daily_return",
                }
            )

            # 处理日期
            df["nav_date"] = pd.to_datetime(df["nav_date"])

            # 筛选日期范围
            if start_date:
                df = df[df["nav_date"] >= pd.Timestamp(start_date)]
            if end_date:
                df = df[df["nav_date"] <= pd.Timestamp(end_date)]

            # 添加基金代码
            df["fund_code"] = fund_code

            # 计算累计净值(如果没有)
            if "accumulated_nav" not in df.columns:
                df["accumulated_nav"] = df["unit_nav"]

            # 选择输出列
            result_df = df[
                ["fund_code", "nav_date", "unit_nav", "accumulated_nav", "daily_return"]
            ].copy()
            result_df = result_df.sort_values("nav_date").reset_index(drop=True)

            # 缓存结果
            if self._cache:
                self._cache.set_fund_nav(fund_code, start_str, end_str, result_df)

            return result_df

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金净值失败: {e}") from e

    def search_funds(
        self,
        fund_type: str | None = None,
        company: str | None = None,
        min_scale: float | None = None,
        max_scale: float | None = None,
        keyword: str | None = None,
        limit: int = 100,
    ) -> pd.DataFrame:
        """
        搜索/筛选基金

        Args:
            fund_type: 基金类型
            company: 基金公司
            min_scale: 最小规模
            max_scale: 最大规模
            keyword: 关键词
            limit: 返回数量限制

        Returns:
            基金列表 DataFrame
        """
        ak = self._get_akshare()

        try:
            # 获取全部开放式基金列表
            df = ak.fund_open_fund_daily_em()

            # 筛选条件
            if fund_type:
                df = df[df["基金类型"].str.contains(fund_type, na=False)]

            if company:
                df = df[df["基金公司"].str.contains(company, na=False)]

            if keyword:
                mask = df["基金代码"].str.contains(keyword, na=False) | df["基金简称"].str.contains(
                    keyword, na=False
                )
                df = df[mask]

            # 规模筛选
            if min_scale is not None:
                df = df[df["基金规模"] >= min_scale]
            if max_scale is not None:
                df = df[df["基金规模"] <= max_scale]

            # 限制返回数量
            df = df.head(limit)

            # 标准化列名
            df = df.rename(
                columns={
                    "基金代码": "code",
                    "基金简称": "name",
                    "基金类型": "type",
                    "基金规模": "scale",
                    "基金公司": "company",
                }
            )

            return df.reset_index(drop=True)

        except Exception as e:
            raise DataSourceError(f"搜索基金失败: {e}") from e

    def get_fund_list(self, fund_type: str | None = None) -> pd.DataFrame:
        """
        获取基金列表

        Args:
            fund_type: 基金类型筛选

        Returns:
            基金列表 DataFrame
        """
        return self.search_funds(fund_type=fund_type)

    def get_benchmark_nav(
        self,
        benchmark_code: str,
        start_date: date | None = None,
        end_date: date | None = None,
    ) -> pd.DataFrame:
        """
        获取基准指数数据

        Args:
            benchmark_code: 基准指数代码
            start_date: 开始日期
            end_date: 结束日期

        Returns:
            基准数据 DataFrame
        """
        ak = self._get_akshare()

        try:
            # 获取指数数据
            df = ak.stock_zh_index_daily(symbol=f"sh{benchmark_code}")

            if df.empty:
                raise DataNotFoundError(f"指数 {benchmark_code} 不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "date": "nav_date",
                    "close": "unit_nav",
                }
            )

            # 处理日期
            df["nav_date"] = pd.to_datetime(df["nav_date"])

            # 筛选日期范围
            if start_date:
                df = df[df["nav_date"] >= pd.Timestamp(start_date)]
            if end_date:
                df = df[df["nav_date"] <= pd.Timestamp(end_date)]

            # 计算收益率
            df["daily_return"] = df["unit_nav"].pct_change() * 100
            df["fund_code"] = benchmark_code
            df["accumulated_nav"] = df["unit_nav"]

            result_df = df[
                ["fund_code", "nav_date", "unit_nav", "accumulated_nav", "daily_return"]
            ].copy()
            return result_df.sort_values("nav_date").reset_index(drop=True)

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基准数据失败: {e}") from e

    def get_fund_holdings(
        self,
        fund_code: str,
        report_date: date | None = None,
    ) -> pd.DataFrame:
        """获取基金持仓数据"""
        cache_key = f"holdings:{fund_code}:{report_date or 'latest'}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.fund_portfolio_hold_em(symbol=fund_code, date=report_date)
            if df.empty:
                raise DataNotFoundError(f"基金 {fund_code} 持仓数据不存在")
            df = df.rename(
                columns={
                    "季度": "report_date",
                    "股票代码": "stock_code",
                    "股票名称": "stock_name",
                    "占净值比例": "weight",
                    "持股数": "holdings_count",
                    "持仓市值": "market_value",
                }
            )
            if "fund_code" not in df.columns:
                df["fund_code"] = fund_code
            result = df.sort_values("weight", ascending=False).reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=86400)
            return result
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取持仓数据失败: {e}") from e

    def get_fund_manager(self, fund_code: str) -> dict[str, Any]:
        """获取基金经理信息"""
        info = self.get_fund_info(fund_code)
        return {
            "name": info.get("manager", ""),
            "fund_code": fund_code,
            "fund_name": info.get("name", ""),
            "company": info.get("company", ""),
        }

    def get_fund_fee(self, fund_code: str) -> dict[str, Any]:
        """获取基金费率信息"""
        ak = self._get_akshare()
        try:
            df = ak.fund_individual_detail_info_xq(symbol=fund_code)
            if df.empty:
                raise DataNotFoundError(f"基金 {fund_code} 费率信息不存在")
            info_dict = dict(zip(df["item"], df["value"], strict=False))
            return {
                "management_fee": info_dict.get("管理费率", ""),
                "custody_fee": info_dict.get("托管费率", ""),
                "purchase_fee": info_dict.get("申购费率", ""),
                "redeem_fee": info_dict.get("赎回费率", ""),
            }
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取费率信息失败: {e}") from e

    def get_fund_rating(self, fund_code: str) -> int | None:
        """获取基金评级"""
        ak = self._get_akshare()
        try:
            df = ak.fund_individual_detail_info_xq(symbol=fund_code)
            if df.empty:
                return None
            info_dict = dict(zip(df["item"], df["value"], strict=False))
            rating_str = info_dict.get("基金评级", "")
            if not rating_str:
                return None
            import re

            match = re.search(r"(\d+)", str(rating_str))
            return int(match.group(1)) if match else None
        except Exception:
            return None

    def batch_get_fund_nav(
        self,
        fund_codes: list[str],
        start_date: date | None = None,
        end_date: date | None = None,
    ) -> dict[str, pd.DataFrame]:
        """批量获取基金净值数据"""
        results = {}
        for code in fund_codes:
            try:
                results[code] = self.get_fund_nav(code, start_date, end_date)
            except Exception:
                results[code] = pd.DataFrame()
        return results

    @staticmethod
    def _parse_date(date_str: str | None) -> date | None:
        """解析日期字符串"""
        if not date_str:
            return None
        try:
            return datetime.strptime(date_str, "%Y-%m-%d").date()
        except (ValueError, TypeError):
            return None

    @staticmethod
    def _parse_scale(scale_str: str | None) -> float | None:
        """解析规模字符串"""
        if not scale_str:
            return None
        try:
            # 移除单位并转换为浮点数
            scale_str = str(scale_str).replace("亿份", "").replace("亿元", "").strip()
            return float(scale_str)
        except (ValueError, TypeError):
            return None

    def _standardize_columns(self, df: pd.DataFrame) -> pd.DataFrame:
        """标准化DataFrame列名"""
        # 常见列名映射
        column_mapping = {
            "日期": "date",
            "时间": "date",
            "股票代码": "code",
            "代码": "code",
            "股票名称": "name",
            "名称": "name",
            "收盘价": "close",
            "开盘价": "open",
            "最高价": "high",
            "最低价": "low",
            "成交量": "volume",
            "成交额": "amount",
            "涨跌幅": "change_pct",
            "市盈率": "pe",
            "市净率": "pb",
            "股息率": "dividend_yield",
            "ROE": "roe",
            "总市值": "total_mv",
            "流通市值": "circ_mv",
            "主力净流入": "main_net_inflow",
            "小单净流入": "small_net_inflow",
            "中单净流入": "medium_net_inflow",
            "大单净流入": "large_net_inflow",
            "超大单净流入": "xlarge_net_inflow",
            "净流入": "net_inflow",
            "当日净流入": "net_inflow",
        }
        # 只重命名存在的列
        rename_dict = {k: v for k, v in column_mapping.items() if k in df.columns}
        if rename_dict:
            df = df.rename(columns=rename_dict)
        return df

    # ==================== P0 级别接口实现 ====================

    def get_all_fund_names(self) -> pd.DataFrame:
        """
        获取所有基金名称列表

        Returns:
            基金名称列表 DataFrame
            列: code, pinyin_abbr, name, type, pinyin_full
        """
        cache_key = "fund_all_names"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.fund_name_em()
            if df.empty:
                raise DataNotFoundError("无法获取基金名称列表")

            # 标准化列名
            df = df.rename(
                columns={
                    "基金代码": "code",
                    "拼音缩写": "pinyin_abbr",
                    "基金简称": "name",
                    "基金类型": "type",
                    "拼音全称": "pinyin_full",
                }
            )

            result = df.reset_index(drop=True)

            # 缓存结果
            if self._cache:
                self._cache.set(cache_key, result, ttl=86400)  # 缓存1天

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金名称列表失败: {e}") from e

    def get_fund_info_ths(self, code: str) -> dict[str, Any]:
        """
        获取同花顺基金基本信息

        Args:
            code: 基金代码

        Returns:
            基金基本信息字典
        """
        cache_key = f"fund_info_ths:{code}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)  # type: ignore[return-value]

        ak = self._get_akshare()
        try:
            df = ak.fund_info_ths(symbol=code)
            if df.empty:
                raise DataNotFoundError(f"基金 {code} 不存在")

            # 转换为字典
            info_dict = dict(zip(df["字段"], df["值"], strict=False))

            result = {
                "code": code,
                "name": info_dict.get("基金简称", ""),
                "full_name": info_dict.get("基金全称", ""),
                "type": info_dict.get("基金类型", ""),
                "investment_type": info_dict.get("投资类型", ""),
                "manager": info_dict.get("基金经理", ""),
                "establish_date": self._parse_date(info_dict.get("成立日期")),
                "establish_scale": info_dict.get("成立规模", ""),
                "management_fee": info_dict.get("管理费", ""),
                "share_scale": info_dict.get("份额规模", ""),
                "custody_fee": info_dict.get("托管费", ""),
                "management_company": info_dict.get("基金管理人", ""),
                "custodian": info_dict.get("基金托管人", ""),
                "subscription_fee": info_dict.get("认购费", ""),
                "purchase_fee": info_dict.get("申购费", ""),
                "redemption_fee": info_dict.get("赎回费", ""),
                "benchmark": info_dict.get("业绩比较基准", ""),
            }

            # 缓存结果
            if self._cache:
                self._cache.set(cache_key, result, ttl=86400)

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取同花顺基金信息失败: {e}") from e

    def get_index_fund_info(
        self,
        category: str = "全部",
        indicator: str = "全部",
    ) -> pd.DataFrame:
        """
        获取指数型基金基本信息

        Args:
            category: 分类,可选 {"全部","沪深指数","行业主题","大盘指数","中盘指数","小盘指数","股票指数","债券指数"}
            indicator: 类型,可选 {"全部","被动指数型","增强指数型"}

        Returns:
            指数型基金信息 DataFrame
        """
        cache_key = f"index_fund_info:{category}:{indicator}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.fund_info_index_em(symbol=category, indicator=indicator)
            if df.empty:
                raise DataNotFoundError("无法获取指数型基金信息")

            # 标准化列名
            df = df.rename(
                columns={
                    "基金代码": "code",
                    "基金名称": "name",
                    "单位净值": "unit_nav",
                    "日期": "nav_date",
                    "日增长率": "daily_return",
                    "近1周": "return_1w",
                    "近1月": "return_1m",
                    "近3月": "return_3m",
                    "近6月": "return_6m",
                    "近1年": "return_1y",
                    "近2年": "return_2y",
                    "近3年": "return_3y",
                    "今年来": "return_ytd",
                    "成立来": "return_since_inception",
                    "手续费": "fee",
                    "起购金额": "min_purchase",
                    "跟踪标的": "tracking_target",
                    "跟踪方式": "tracking_method",
                }
            )

            result = df.reset_index(drop=True)

            # 缓存结果
            if self._cache:
                self._cache.set(cache_key, result, ttl=3600)  # 缓存1小时

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取指数型基金信息失败: {e}") from e

    def get_fund_overview(self, code: str) -> dict[str, Any]:
        """
        获取基金档案基本概况

        Args:
            code: 基金代码

        Returns:
            基金概况字典
        """
        cache_key = f"fund_overview:{code}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)  # type: ignore[return-value]

        ak = self._get_akshare()
        try:
            df = ak.fund_overview_em(symbol=code)
            if df.empty:
                raise DataNotFoundError(f"基金 {code} 概况不存在")

            # 转换为字典
            result = dict(zip(df["Key"], df["Value"], strict=False))
            result["code"] = code

            # 缓存结果
            if self._cache:
                self._cache.set(cache_key, result, ttl=86400)

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金概况失败: {e}") from e

    def get_fund_purchase_status(self) -> pd.DataFrame:
        """
        获取基金申购/赎回状态

        Returns:
            基金申购赎回状态 DataFrame
        """
        cache_key = "fund_purchase_status"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.fund_purchase_em()
            if df.empty:
                raise DataNotFoundError("无法获取基金申购赎回状态")

            # 标准化列名
            df = df.rename(
                columns={
                    "基金代码": "code",
                    "基金简称": "name",
                    "基金类型": "type",
                    "最新净值/万份收益": "latest_nav",
                    "报告时间": "report_time",
                    "申购状态": "purchase_status",
                    "赎回状态": "redemption_status",
                    "下一开放日": "next_open_day",
                    "购买起点": "min_purchase",
                    "日累计限定金额": "daily_limit",
                    "手续费": "fee",
                }
            )

            result = df.reset_index(drop=True)

            # 缓存结果(短期缓存,因为状态可能变化)
            if self._cache:
                self._cache.set(cache_key, result, ttl=1800)  # 缓存30分钟

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金申购赎回状态失败: {e}") from e

    def get_fund_daily_nav(self) -> pd.DataFrame:
        """
        获取开放式基金每日净值(全部)

        Returns:
            每日净值 DataFrame
        """
        cache_key = "fund_daily_nav"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.fund_open_fund_daily_em()
            if df.empty:
                raise DataNotFoundError("无法获取基金每日净值")

            # 标准化列名
            df = df.rename(
                columns={
                    "基金代码": "code",
                    "基金简称": "name",
                    "当日单位净值": "unit_nav",
                    "当日累计净值": "accumulated_nav",
                    "前日单位净值": "prev_unit_nav",
                    "前日累计净值": "prev_accumulated_nav",
                    "日增长值": "daily_change",
                    "日增长率": "daily_return",
                    "申购状态": "purchase_status",
                    "赎回状态": "redemption_status",
                    "手续费": "fee",
                }
            )

            result = df.reset_index(drop=True)

            # 缓存结果
            if self._cache:
                self._cache.set(cache_key, result, ttl=3600)  # 缓存1小时

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金每日净值失败: {e}") from e

    def get_etf_spot(self) -> pd.DataFrame:
        """
        获取ETF实时行情

        Returns:
            ETF实时行情 DataFrame
        """
        cache_key = "etf_spot"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.fund_etf_spot_em()
            if df.empty:
                raise DataNotFoundError("无法获取ETF实时行情")

            # 标准化列名(转换为snake_case)
            column_mapping = {
                "代码": "code",
                "名称": "name",
                "最新价": "latest_price",
                "IOPV实时估值": "iopv",
                "基金折价率": "discount_rate",
                "涨跌额": "change_amount",
                "涨跌幅": "change_pct",
                "成交量": "volume",
                "成交额": "turnover",
                "开盘价": "open",
                "最高价": "high",
                "最低价": "low",
                "昨收": "prev_close",
                "换手率": "turnover_rate",
                "量比": "volume_ratio",
                "委比": "order_ratio",
                "外盘": "outer_volume",
                "内盘": "inner_volume",
                "主力净流入-净额": "main_net_inflow",
                "主力净流入-净占比": "main_net_inflow_pct",
                "超大单净流入-净额": "super_large_net_inflow",
                "超大单净流入-净占比": "super_large_net_inflow_pct",
                "大单净流入-净额": "large_net_inflow",
                "大单净流入-净占比": "large_net_inflow_pct",
                "中单净流入-净额": "medium_net_inflow",
                "中单净流入-净占比": "medium_net_inflow_pct",
                "小单净流入-净额": "small_net_inflow",
                "小单净流入-净占比": "small_net_inflow_pct",
                "现手": "current_hand",
                "买一": "bid1",
                "卖一": "ask1",
                "最新份额": "latest_shares",
                "流通市值": "circulating_market_cap",
                "总市值": "total_market_cap",
                "数据日期": "data_date",
                "更新时间": "update_time",
            }

            df = df.rename(columns=column_mapping)
            result = df.reset_index(drop=True)

            # 缓存结果(短期缓存)
            if self._cache:
                self._cache.set(cache_key, result, ttl=300)  # 缓存5分钟

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取ETF实时行情失败: {e}") from e

    def get_fund_category_spot(
        self,
        category: str = "",
        date: str | None = None,
    ) -> pd.DataFrame:
        """
        获取同花顺基金实时行情(按类型)

        Args:
            category: 基金类型,可选 {"股票型","债券型","混合型","ETF","LOF","QDII","保本型","指数型",""}
            date: 日期,格式 YYYYMMDD,默认为今天

        Returns:
            基金实时行情 DataFrame
        """
        if date is None:
            date = datetime.now().strftime("%Y%m%d")

        cache_key = f"fund_category_spot:{category}:{date}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.fund_etf_category_ths(symbol=category, date=date)
            if df.empty:
                raise DataNotFoundError(f"无法获取 {category} 类型基金实时行情")

            # 标准化列名
            df = df.rename(
                columns={
                    "基金代码": "code",
                    "基金名称": "name",
                    "当前单位净值": "current_unit_nav",
                    "当前累计净值": "current_accumulated_nav",
                    "前一日单位净值": "prev_unit_nav",
                    "前一日累计净值": "prev_accumulated_nav",
                    "增长值": "change_value",
                    "增长率": "change_rate",
                    "赎回状态": "redemption_status",
                    "申购状态": "purchase_status",
                    "最新交易日": "latest_trade_date",
                    "最新单位净值": "latest_unit_nav",
                    "最新累计净值": "latest_accumulated_nav",
                    "基金类型": "type",
                    "查询日期": "query_date",
                }
            )

            result = df.reset_index(drop=True)

            # 缓存结果
            if self._cache:
                self._cache.set(cache_key, result, ttl=1800)  # 缓存30分钟

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金分类实时行情失败: {e}") from e

    def get_etf_spot_ths(self, date: str | None = None) -> pd.DataFrame:
        """
        获取同花顺ETF实时行情

        Args:
            date: 日期,格式 YYYYMMDD,默认为今天

        Returns:
            ETF实时行情 DataFrame
        """
        if date is None:
            date = datetime.now().strftime("%Y%m%d")

        cache_key = f"etf_spot_ths:{date}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.fund_etf_spot_ths(date=date)
            if df.empty:
                raise DataNotFoundError("无法获取同花顺ETF实时行情")

            # 标准化列名
            df = df.rename(
                columns={
                    "基金代码": "code",
                    "基金名称": "name",
                    "当前单位净值": "current_unit_nav",
                    "当前累计净值": "current_accumulated_nav",
                    "前一日单位净值": "prev_unit_nav",
                    "前一日累计净值": "prev_accumulated_nav",
                    "增长值": "change_value",
                    "增长率": "change_rate",
                    "赎回状态": "redemption_status",
                    "申购状态": "purchase_status",
                    "最新交易日": "latest_trade_date",
                    "最新单位净值": "latest_unit_nav",
                    "最新累计净值": "latest_accumulated_nav",
                    "基金类型": "type",
                    "查询日期": "query_date",
                }
            )

            result = df.reset_index(drop=True)

            # 缓存结果
            if self._cache:
                self._cache.set(cache_key, result, ttl=1800)  # 缓存30分钟

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取同花顺ETF实时行情失败: {e}") from e

    def get_lof_spot(self) -> pd.DataFrame:
        """
        获取LOF实时行情

        Returns:
            LOF实时行情 DataFrame
        """
        cache_key = "lof_spot"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.fund_lof_spot_em()
            if df.empty:
                raise DataNotFoundError("无法获取LOF实时行情")

            # 标准化列名
            df = df.rename(
                columns={
                    "代码": "code",
                    "名称": "name",
                    "最新价": "latest_price",
                    "涨跌额": "change_amount",
                    "涨跌幅": "change_pct",
                    "成交量": "volume",
                    "成交额": "turnover",
                    "开盘价": "open",
                    "最高价": "high",
                    "最低价": "low",
                    "昨收": "prev_close",
                    "换手率": "turnover_rate",
                    "流通市值": "circulating_market_cap",
                    "总市值": "total_market_cap",
                }
            )

            result = df.reset_index(drop=True)

            # 缓存结果
            if self._cache:
                self._cache.set(cache_key, result, ttl=300)  # 缓存5分钟

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取LOF实时行情失败: {e}") from e

    def get_etf_hist(
        self,
        code: str,
        period: str = "daily",
        start: str | None = None,
        end: str | None = None,
    ) -> pd.DataFrame:
        """
        获取ETF历史行情

        Args:
            code: ETF代码
            period: 周期,可选 {"daily","weekly","monthly"}
            start: 开始日期,格式 YYYYMMDD
            end: 结束日期,格式 YYYYMMDD

        Returns:
            ETF历史行情 DataFrame
        """
        if end is None:
            end = datetime.now().strftime("%Y%m%d")
        if start is None:
            # 默认获取1年数据
            from datetime import timedelta
            start = (datetime.now() - timedelta(days=365)).strftime("%Y%m%d")

        cache_key = f"etf_hist:{code}:{period}:{start}:{end}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.fund_etf_hist_em(
                symbol=code,
                period=period,
                start_date=start,
                end_date=end,
            )
            if df.empty:
                raise DataNotFoundError(f"ETF {code} 历史行情不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "日期": "date",
                    "开盘": "open",
                    "收盘": "close",
                    "最高": "high",
                    "最低": "low",
                    "成交量": "volume",
                    "成交额": "turnover",
                    "振幅": "amplitude",
                    "涨跌幅": "change_pct",
                    "涨跌额": "change_amount",
                    "换手率": "turnover_rate",
                }
            )

            df["code"] = code
            result = df.reset_index(drop=True)

            # 缓存结果
            if self._cache:
                self._cache.set(cache_key, result, ttl=86400)  # 缓存1天

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取ETF历史行情失败: {e}") from e

    def get_lof_hist(
        self,
        code: str,
        period: str = "daily",
        start: str | None = None,
        end: str | None = None,
    ) -> pd.DataFrame:
        """
        获取LOF历史行情

        Args:
            code: LOF代码
            period: 周期,可选 {"daily","weekly","monthly"}
            start: 开始日期,格式 YYYYMMDD
            end: 结束日期,格式 YYYYMMDD

        Returns:
            LOF历史行情 DataFrame
        """
        if end is None:
            end = datetime.now().strftime("%Y%m%d")
        if start is None:
            from datetime import timedelta
            start = (datetime.now() - timedelta(days=365)).strftime("%Y%m%d")

        cache_key = f"lof_hist:{code}:{period}:{start}:{end}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.fund_lof_hist_em(
                symbol=code,
                period=period,
                start_date=start,
                end_date=end,
            )
            if df.empty:
                raise DataNotFoundError(f"LOF {code} 历史行情不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "日期": "date",
                    "开盘": "open",
                    "收盘": "close",
                    "最高": "high",
                    "最低": "low",
                    "成交量": "volume",
                    "成交额": "turnover",
                    "振幅": "amplitude",
                    "涨跌幅": "change_pct",
                    "涨跌额": "change_amount",
                    "换手率": "turnover_rate",
                }
            )

            df["code"] = code
            result = df.reset_index(drop=True)

            # 缓存结果
            if self._cache:
                self._cache.set(cache_key, result, ttl=86400)

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取LOF历史行情失败: {e}") from e

    def get_etf_minute(
        self,
        code: str,
        period: str = "1",
        start: str | None = None,
        end: str | None = None,
    ) -> pd.DataFrame:
        """
        获取ETF分时行情

        Args:
            code: ETF代码
            period: 分钟周期,可选 {"1","5","15","30","60"}
            start: 开始日期,格式 YYYYMMDD
            end: 结束日期,格式 YYYYMMDD

        Returns:
            ETF分时行情 DataFrame
        """
        if end is None:
            end = datetime.now().strftime("%Y%m%d")
        if start is None:
            start = datetime.now().strftime("%Y%m%d")

        cache_key = f"etf_minute:{code}:{period}:{start}:{end}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.fund_etf_hist_min_em(
                symbol=code,
                period=period,
                start_date=start,
                end_date=end,
            )
            if df.empty:
                raise DataNotFoundError(f"ETF {code} 分时行情不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "时间": "time",
                    "开盘": "open",
                    "收盘": "close",
                    "最高": "high",
                    "最低": "low",
                    "成交量": "volume",
                    "成交额": "turnover",
                    "均价": "avg_price",
                }
            )

            df["code"] = code
            result = df.reset_index(drop=True)

            # 缓存结果(短期缓存)
            if self._cache:
                self._cache.set(cache_key, result, ttl=300)  # 缓存5分钟

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取ETF分时行情失败: {e}") from e

    def get_lof_minute(
        self,
        code: str,
        period: str = "1",
        start: str | None = None,
        end: str | None = None,
    ) -> pd.DataFrame:
        """
        获取LOF分时行情

        Args:
            code: LOF代码
            period: 分钟周期,可选 {"1","5","15","30","60"}
            start: 开始日期,格式 YYYYMMDD
            end: 结束日期,格式 YYYYMMDD

        Returns:
            LOF分时行情 DataFrame
        """
        if end is None:
            end = datetime.now().strftime("%Y%m%d")
        if start is None:
            start = datetime.now().strftime("%Y%m%d")

        cache_key = f"lof_minute:{code}:{period}:{start}:{end}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.fund_lof_hist_min_em(
                symbol=code,
                period=period,
                start_date=start,
                end_date=end,
            )
            if df.empty:
                raise DataNotFoundError(f"LOF {code} 分时行情不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "时间": "time",
                    "开盘": "open",
                    "收盘": "close",
                    "最高": "high",
                    "最低": "low",
                    "成交量": "volume",
                    "成交额": "turnover",
                    "均价": "avg_price",
                }
            )

            df["code"] = code
            result = df.reset_index(drop=True)

            # 缓存结果
            if self._cache:
                self._cache.set(cache_key, result, ttl=300)

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取LOF分时行情失败: {e}") from e

    def get_fund_bond_holdings(
        self,
        code: str,
        year: int | None = None,  # type: ignore[override]
    ) -> pd.DataFrame:
        """
        获取基金债券持仓

        Args:
            code: 基金代码
            year: 年份,如 2024,默认为当前年份

        Returns:
            债券持仓 DataFrame
        """
        if year is None:
            year = datetime.now().year

        cache_key = f"fund_bond_holdings:{code}:{year}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.fund_portfolio_bond_hold_em(symbol=code, date=str(year))
            if df.empty:
                raise DataNotFoundError(f"基金 {code} 债券持仓数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "序号": "seq",
                    "债券代码": "bond_code",
                    "债券名称": "bond_name",
                    "占净值比例": "weight",
                    "持仓市值(万元)": "market_value",
                    "季度": "quarter",
                }
            )

            df["fund_code"] = code
            result = df.reset_index(drop=True)

            # 缓存结果
            if self._cache:
                self._cache.set(cache_key, result, ttl=86400)

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金债券持仓失败: {e}") from e

    def get_fund_industry_allocation(
        self,
        code: str,
        year: int | None = None,  # type: ignore[override]
    ) -> pd.DataFrame:
        """
        获取基金行业配置

        Args:
            code: 基金代码
            year: 年份,如 2024,默认为当前年份

        Returns:
            行业配置 DataFrame
        """
        if year is None:
            year = datetime.now().year

        cache_key = f"fund_industry_allocation:{code}:{year}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.fund_portfolio_industry_allocation_em(symbol=code, date=str(year))
            if df.empty:
                raise DataNotFoundError(f"基金 {code} 行业配置数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "序号": "seq",
                    "行业类别": "industry",
                    "占净值比例": "weight",
                    "市值": "market_value",
                    "截止时间": "report_date",
                }
            )

            df["fund_code"] = code
            result = df.reset_index(drop=True)

            # 缓存结果
            if self._cache:
                self._cache.set(cache_key, result, ttl=86400)

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金行业配置失败: {e}") from e

    def get_fund_portfolio_change(
        self,
        code: str,
        indicator: str = "累计买入",
        year: int | None = None,  # type: ignore[override]
    ) -> pd.DataFrame:
        """
        获取基金重大变动(累计买入/卖出)

        Args:
            code: 基金代码
            indicator: 指标类型,可选 {"累计买入","累计卖出"}
            year: 年份,如 2024,默认为当前年份

        Returns:
            重大变动 DataFrame
        """
        if year is None:
            year = datetime.now().year

        cache_key = f"fund_portfolio_change:{code}:{indicator}:{year}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.fund_portfolio_change_em(
                symbol=code,
                indicator=indicator,
                date=str(year),
            )
            if df.empty:
                raise DataNotFoundError(f"基金 {code} 重大变动数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "序号": "seq",
                    "股票代码": "stock_code",
                    "股票名称": "stock_name",
                    "本期累计买入/卖出金额": "amount",
                    "占期初基金资产净值比例": "weight",
                    "季度": "quarter",
                }
            )

            df["fund_code"] = code
            df["indicator"] = indicator
            result = df.reset_index(drop=True)

            # 缓存结果
            if self._cache:
                self._cache.set(cache_key, result, ttl=86400)

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金重大变动失败: {e}") from e

    def get_all_fund_managers(self) -> pd.DataFrame:
        """
        获取基金经理大全

        Returns:
            基金经理列表 DataFrame
        """
        cache_key = "fund_all_managers"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.fund_manager_em()
            if df.empty:
                raise DataNotFoundError("无法获取基金经理数据")

            # 标准化列名
            df = df.rename(
                columns={
                    "序号": "seq",
                    "姓名": "name",
                    "所属公司": "company",
                    "现任基金代码": "current_fund_codes",
                    "现任基金": "current_funds",
                    "累计从业时间": "total_experience",
                    "现任基金资产总规模": "total_scale",
                    "现任基金最佳回报": "best_return",
                }
            )

            result = df.reset_index(drop=True)

            # 缓存结果
            if self._cache:
                self._cache.set(cache_key, result, ttl=86400)  # 缓存1天

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金经理大全失败: {e}") from e

    # ==================== P2 辅助分析接口 ====================

    # -------------------- 宏观经济数据(22个) --------------------

    def get_macro_leverage_ratio(self) -> pd.DataFrame:
        """
        获取中国宏观杠杆率数据

        Returns:
            宏观杠杆率 DataFrame
        """
        cache_key = "macro:leverage_ratio"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_cnbs()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取宏观杠杆率数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取宏观杠杆率失败: {e}") from e

    def get_enterprise_price_index(self) -> pd.DataFrame:
        """
        获取企业商品价格指数

        Returns:
            企业商品价格指数 DataFrame
        """
        cache_key = "macro:enterprise_price_index"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_qyspjg()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取企业商品价格指数数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取企业商品价格指数失败: {e}") from e

    def get_fdi_data(self) -> pd.DataFrame:
        """
        获取外商直接投资数据

        Returns:
            FDI数据 DataFrame
        """
        cache_key = "macro:fdi_data"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_fdi()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取外商直接投资数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取FDI数据失败: {e}") from e

    def get_lpr_data(self) -> pd.DataFrame:
        """
        获取LPR品种数据

        Returns:
            LPR数据 DataFrame
        """
        cache_key = "macro:lpr_data"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_lpr()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取LPR数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取LPR数据失败: {e}") from e

    def get_urban_unemployment(self) -> pd.DataFrame:
        """
        获取城镇调查失业率

        Returns:
            失业率数据 DataFrame
        """
        cache_key = "macro:urban_unemployment"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_urban_unemployment()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取城镇失业率数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取城镇失业率失败: {e}") from e

    def get_social_financing(self) -> pd.DataFrame:
        """
        获取社会融资规模增量统计

        Returns:
            社融数据 DataFrame
        """
        cache_key = "macro:social_financing"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_shrzgm()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取社会融资规模数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取社会融资规模失败: {e}") from e

    def get_gdp_yearly(self) -> pd.DataFrame:
        """
        获取中国GDP年率数据

        Returns:
            GDP年率 DataFrame
        """
        cache_key = "macro:gdp_yearly"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_gdp_yearly()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取GDP年率数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取GDP年率失败: {e}") from e

    def get_gdp_quarterly(self) -> pd.DataFrame:
        """
        获取中国GDP季度数据

        Returns:
            GDP季度数据 DataFrame
        """
        cache_key = "macro:gdp_quarterly"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_gdp_quarterly()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取GDP季度数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取GDP季度数据失败: {e}") from e

    def get_cpi_yearly(self) -> pd.DataFrame:
        """
        获取中国CPI年率数据

        Returns:
            CPI年率 DataFrame
        """
        cache_key = "macro:cpi_yearly"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_cpi_yearly()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取CPI年率数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取CPI年率失败: {e}") from e

    def get_cpi_monthly(self) -> pd.DataFrame:
        """
        获取中国CPI月率数据

        Returns:
            CPI月率 DataFrame
        """
        cache_key = "macro:cpi_monthly"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_cpi_monthly()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取CPI月率数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取CPI月率失败: {e}") from e

    def get_ppi_yearly(self) -> pd.DataFrame:
        """
        获取中国PPI年率数据

        Returns:
            PPI年率 DataFrame
        """
        cache_key = "macro:ppi_yearly"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_ppi_yearly()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取PPI年率数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取PPI年率失败: {e}") from e

    def get_ppi_monthly(self) -> pd.DataFrame:
        """
        获取中国PPI月率数据

        Returns:
            PPI月率 DataFrame
        """
        cache_key = "macro:ppi_monthly"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_ppi_monthly()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取PPI月率数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取PPI月率失败: {e}") from e

    def get_exports_yearly(self) -> pd.DataFrame:
        """
        获取出口年率数据

        Returns:
            出口年率 DataFrame
        """
        cache_key = "macro:exports_yearly"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_exports_yearly()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取出口年率数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取出口年率失败: {e}") from e

    def get_imports_yearly(self) -> pd.DataFrame:
        """
        获取进口年率数据

        Returns:
            进口年率 DataFrame
        """
        cache_key = "macro:imports_yearly"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_imports_yearly()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取进口年率数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取进口年率失败: {e}") from e

    def get_trade_balance(self) -> pd.DataFrame:
        """
        获取贸易帐数据

        Returns:
            贸易帐 DataFrame
        """
        cache_key = "macro:trade_balance"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_trade_balance()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取贸易帐数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取贸易帐失败: {e}") from e

    def get_industrial_production(self) -> pd.DataFrame:
        """
        获取工业增加值增长数据

        Returns:
            工业增加值 DataFrame
        """
        cache_key = "macro:industrial_production"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_industrial_production_yearly()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取工业增加值数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取工业增加值失败: {e}") from e

    def get_pmi_official(self) -> pd.DataFrame:
        """
        获取官方制造业PMI数据

        Returns:
            官方PMI DataFrame
        """
        cache_key = "macro:pmi_official"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_pmi_yearly()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取官方PMI数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取官方PMI失败: {e}") from e

    def get_pmi_caixin(self) -> pd.DataFrame:
        """
        获取财新制造业PMI数据

        Returns:
            财新PMI DataFrame
        """
        cache_key = "macro:pmi_caixin"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_cx_pmi_yearly()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取财新PMI数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取财新PMI失败: {e}") from e

    def get_services_pmi(self) -> pd.DataFrame:
        """
        获取财新服务业PMI数据

        Returns:
            服务业PMI DataFrame
        """
        cache_key = "macro:services_pmi"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_cx_services_pmi()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取服务业PMI数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取服务业PMI失败: {e}") from e

    def get_non_manufacturing_pmi(self) -> pd.DataFrame:
        """
        获取官方非制造业PMI数据

        Returns:
            非制造业PMI DataFrame
        """
        cache_key = "macro:non_manufacturing_pmi"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_non_man_pmi()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取非制造业PMI数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取非制造业PMI失败: {e}") from e

    def get_m2_yearly(self) -> pd.DataFrame:
        """
        获取M2货币供应年率数据

        Returns:
            M2数据 DataFrame
        """
        cache_key = "macro:m2_yearly"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_m2_yearly()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取M2货币供应数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取M2数据失败: {e}") from e

    def get_new_loan(self) -> pd.DataFrame:
        """
        获取新增人民币贷款数据

        Returns:
            新增贷款 DataFrame
        """
        cache_key = "macro:new_loan"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_new_loan()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取新增贷款数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取新增贷款失败: {e}") from e

    def get_retail_sales_yearly(self) -> pd.DataFrame:
        """
        获取社会消费品零售总额年率数据

        Returns:
            零售销售 DataFrame
        """
        cache_key = "macro:retail_sales_yearly"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_retail_sales_yearly()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取零售销售数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取零售销售失败: {e}") from e

    def get_fixed_asset_investment(self) -> pd.DataFrame:
        """
        获取固定资产投资年率数据

        Returns:
            固定资产投资 DataFrame
        """
        cache_key = "macro:fixed_asset_investment"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_fixed_asset_investment_yearly()

            if df is None or df.empty:
                raise DataNotFoundError("无法获取固定资产投资数据")

            # 列名标准化
            df = self._standardize_columns(df)

            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)

            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取固定资产投资失败: {e}") from e

    # -------------------- 利率数据(8个) --------------------

    def get_china_interest_rate(self) -> pd.DataFrame:
        """
        获取中国央行利率决议数据

        Returns:
            中国利率 DataFrame
        """
        cache_key = "interest_rate:china"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_bank_china_interest_rate()
            if df.empty:
                raise DataNotFoundError("中国央行利率数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "日期": "date",
                    "利率": "rate",
                    "利率类型": "rate_type",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=3600)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取中国利率失败: {e}") from e

    def get_usa_interest_rate(self) -> pd.DataFrame:
        """
        获取美联储利率决议数据

        Returns:
            美国利率 DataFrame
        """
        cache_key = "interest_rate:usa"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_bank_usa_interest_rate()
            if df.empty:
                raise DataNotFoundError("美联储利率数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "日期": "date",
                    "利率": "rate",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=3600)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取美国利率失败: {e}") from e

    def get_euro_interest_rate(self) -> pd.DataFrame:
        """
        获取欧洲央行利率决议数据

        Returns:
            欧元区利率 DataFrame
        """
        cache_key = "interest_rate:euro"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_bank_euro_interest_rate()
            if df.empty:
                raise DataNotFoundError("欧洲央行利率数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "日期": "date",
                    "利率": "rate",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=3600)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取欧元区利率失败: {e}") from e

    def get_japan_interest_rate(self) -> pd.DataFrame:
        """
        获取日本央行利率决议数据

        Returns:
            日本利率 DataFrame
        """
        cache_key = "interest_rate:japan"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_bank_japan_interest_rate()
            if df.empty:
                raise DataNotFoundError("日本央行利率数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "日期": "date",
                    "利率": "rate",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=3600)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取日本利率失败: {e}") from e

    def get_uk_interest_rate(self) -> pd.DataFrame:
        """
        获取英国央行利率决议数据

        Returns:
            英国利率 DataFrame
        """
        cache_key = "interest_rate:uk"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_bank_uk_interest_rate()
            if df.empty:
                raise DataNotFoundError("英国央行利率数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "日期": "date",
                    "利率": "rate",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=3600)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取英国利率失败: {e}") from e

    def get_shibor(self) -> pd.DataFrame:
        """
        获取SHIBOR利率数据

        Returns:
            SHIBOR DataFrame
        """
        cache_key = "interest_rate:shibor"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_shibor()
            if df.empty:
                raise DataNotFoundError("SHIBOR利率数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "日期": "date",
                    "O/N": "overnight",
                    "1W": "week_1",
                    "2W": "week_2",
                    "1M": "month_1",
                    "3M": "month_3",
                    "6M": "month_6",
                    "9M": "month_9",
                    "1Y": "year_1",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=3600)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取SHIBOR失败: {e}") from e

    def get_shibor_lpr(self) -> pd.DataFrame:
        """
        获取SHIBOR-LPR数据

        Returns:
            SHIBOR-LPR DataFrame
        """
        cache_key = "interest_rate:shibor_lpr"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_shibor_lpr()
            if df.empty:
                raise DataNotFoundError("SHIBOR-LPR数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "日期": "date",
                    "LPR1Y": "lpr_1y",
                    "LPR5Y": "lpr_5y",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=3600)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取SHIBOR-LPR失败: {e}") from e

    def get_hibor(self) -> pd.DataFrame:
        """
        获取HIBOR利率数据

        Returns:
            HIBOR DataFrame
        """
        cache_key = "interest_rate:hibor"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.macro_china_hibor()
            if df.empty:
                raise DataNotFoundError("HIBOR利率数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "日期": "date",
                    "O/N": "overnight",
                    "1W": "week_1",
                    "2W": "week_2",
                    "1M": "month_1",
                    "3M": "month_3",
                    "6M": "month_6",
                    "9M": "month_9",
                    "1Y": "year_1",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=3600)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取HIBOR失败: {e}") from e

    # -------------------- 行业板块(5个) --------------------

    def get_industry_boards(self) -> pd.DataFrame:
        """
        获取行业板块名称列表

        Returns:
            行业板块列表 DataFrame
        """
        cache_key = "industry_boards:list"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.stock_board_industry_name_em()
            if df.empty:
                raise DataNotFoundError("行业板块数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "板块名称": "name",
                    "板块代码": "code",
                    "最新价": "price",
                    "涨跌幅": "change_pct",
                    "涨跌额": "change",
                    "成交量": "volume",
                    "成交额": "amount",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=300)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取行业板块列表失败: {e}") from e

    def get_industry_board_hist(
        self,
        code: str,
        period: str = "daily",
        start_date: str | None = None,
        end_date: str | None = None,
    ) -> pd.DataFrame:
        """
        获取行业板块历史行情

        Args:
            code: 板块代码
            period: 周期 (daily/weekly/monthly)
            start_date: 开始日期 (YYYYMMDD)
            end_date: 结束日期 (YYYYMMDD)

        Returns:
            行业板块历史数据 DataFrame
        """
        cache_key = f"industry_board_hist:{code}:{period}:{start_date}:{end_date}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.stock_board_industry_hist_em(
                symbol=code, period=period, start_date=start_date, end_date=end_date
            )
            if df.empty:
                raise DataNotFoundError(f"行业板块 {code} 历史数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "日期": "date",
                    "开盘": "open",
                    "收盘": "close",
                    "最高": "high",
                    "最低": "low",
                    "成交量": "volume",
                    "成交额": "amount",
                    "振幅": "amplitude",
                    "涨跌幅": "change_pct",
                    "涨跌额": "change",
                    "换手率": "turnover",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=300)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取行业板块历史数据失败: {e}") from e

    def get_concept_boards(self) -> pd.DataFrame:
        """
        获取概念板块名称列表

        Returns:
            概念板块列表 DataFrame
        """
        cache_key = "concept_boards:list"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.stock_board_concept_name_em()
            if df.empty:
                raise DataNotFoundError("概念板块数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "板块名称": "name",
                    "板块代码": "code",
                    "最新价": "price",
                    "涨跌幅": "change_pct",
                    "涨跌额": "change",
                    "成交量": "volume",
                    "成交额": "amount",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=300)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取概念板块列表失败: {e}") from e

    def get_concept_board_hist(
        self,
        code: str,
        period: str = "daily",
        start_date: str | None = None,
        end_date: str | None = None,
    ) -> pd.DataFrame:
        """
        获取概念板块历史行情

        Args:
            code: 板块代码
            period: 周期 (daily/weekly/monthly)
            start_date: 开始日期 (YYYYMMDD)
            end_date: 结束日期 (YYYYMMDD)

        Returns:
            概念板块历史数据 DataFrame
        """
        cache_key = f"concept_board_hist:{code}:{period}:{start_date}:{end_date}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.stock_board_concept_hist_em(
                symbol=code, period=period, start_date=start_date, end_date=end_date
            )
            if df.empty:
                raise DataNotFoundError(f"概念板块 {code} 历史数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "日期": "date",
                    "开盘": "open",
                    "收盘": "close",
                    "最高": "high",
                    "最低": "low",
                    "成交量": "volume",
                    "成交额": "amount",
                    "振幅": "amplitude",
                    "涨跌幅": "change_pct",
                    "涨跌额": "change",
                    "换手率": "turnover",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=300)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取概念板块历史数据失败: {e}") from e

    def get_sector_fund_flow(self, period: str = "今日") -> pd.DataFrame:
        """
        获取板块资金流向

        Args:
            period: 统计周期 ("今日","5日","10日")

        Returns:
            板块资金流向 DataFrame
        """
        cache_key = f"sector_fund_flow:{period}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.stock_sector_spot(indicator=period)
            if df.empty:
                raise DataNotFoundError(f"板块资金流向数据不存在 (周期: {period})")

            # 标准化列名
            df = df.rename(
                columns={
                    "名称": "name",
                    "今日涨跌幅": "change_pct",
                    "今日主力净流入": "main_inflow",
                    "今日小单净流入": "small_inflow",
                    "今日中单净流入": "medium_inflow",
                    "今日大单净流入": "large_inflow",
                    "今日超大单净流入": "xlarge_inflow",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=300)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取板块资金流向失败: {e}") from e

    # -------------------- 债券数据(7个) --------------------

    def get_china_us_bond_yield(self) -> pd.DataFrame:
        """
        获取中美国债收益率数据

        Returns:
            中美国债收益率 DataFrame
        """
        cache_key = "bond_yield:china_us"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.bond_zh_us_rate()
            if df.empty:
                raise DataNotFoundError("中美国债收益率数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "日期": "date",
                    "中国国债收益率2Y": "china_2y",
                    "中国国债收益率5Y": "china_5y",
                    "中国国债收益率10Y": "china_10y",
                    "中国国债收益率30Y": "china_30y",
                    "美国国债收益率2Y": "usa_2y",
                    "美国国债收益率5Y": "usa_5y",
                    "美国国债收益率10Y": "usa_10y",
                    "美国国债收益率30Y": "usa_30y",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=300)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取中美国债收益率失败: {e}") from e

    def get_bond_yield_curve(
        self,
        bond_type: str = "国债",
        period: str = "daily",
        start_date: str | None = None,
        end_date: str | None = None,
    ) -> pd.DataFrame:
        """
        获取收盘收益率曲线历史数据

        Args:
            bond_type: 债券类型 ("国债","国开债","农发债","进出口行债")
            period: 周期
            start_date: 开始日期 (YYYYMMDD)
            end_date: 结束日期 (YYYYMMDD)

        Returns:
            收益率曲线 DataFrame
        """
        cache_key = f"bond_yield_curve:{bond_type}:{period}:{start_date}:{end_date}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.bond_china_close_return(
                symbol=bond_type, period=period, start_date=start_date, end_date=end_date
            )
            if df.empty:
                raise DataNotFoundError(f"债券收益率曲线数据不存在 (类型: {bond_type})")

            # 标准化列名
            df = df.rename(
                columns={
                    "date": "date",
                    "1y": "yield_1y",
                    "2y": "yield_2y",
                    "3y": "yield_3y",
                    "5y": "yield_5y",
                    "7y": "yield_7y",
                    "10y": "yield_10y",
                    "30y": "yield_30y",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=300)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取债券收益率曲线失败: {e}") from e

    def get_bond_spot_quote(self) -> pd.DataFrame:
        """
        获取现券市场做市报价

        Returns:
            现券报价 DataFrame
        """
        cache_key = "bond_spot_quote:list"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.bond_spot_quote()
            if df.empty:
                raise DataNotFoundError("现券报价数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "债券简称": "name",
                    "债券代码": "code",
                    "买入价": "bid_price",
                    "卖出价": "ask_price",
                    "到期收益率": "yield",
                    "剩余期限": "remaining_term",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=300)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取现券报价失败: {e}") from e

    def get_convertible_bonds(self) -> pd.DataFrame:
        """
        获取可转债数据一览表

        Returns:
            可转债列表 DataFrame
        """
        cache_key = "convertible_bonds:list"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.bond_cb_info_jsl()
            if df.empty:
                raise DataNotFoundError("可转债数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "债券代码": "code",
                    "债券简称": "name",
                    "现价": "price",
                    "涨跌幅": "change_pct",
                    "正股代码": "stock_code",
                    "正股名称": "stock_name",
                    "正股价": "stock_price",
                    "转股价": "convert_price",
                    "转股价值": "convert_value",
                    "溢价率": "premium_rate",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=300)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取可转债列表失败: {e}") from e

    def get_convertible_bond_detail(self, code: str) -> pd.DataFrame:
        """
        获取可转债详情数据

        Args:
            code: 可转债代码

        Returns:
            可转债详情 DataFrame
        """
        cache_key = f"convertible_bond_detail:{code}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.bond_cb_detail_jsl(symbol=code)
            if df.empty:
                raise DataNotFoundError(f"可转债 {code} 详情数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "项目": "item",
                    "值": "value",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=300)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取可转债详情失败: {e}") from e

    def get_bond_spot(self, code: str) -> pd.DataFrame:
        """
        获取沪深债券实时行情

        Args:
            code: 债券代码

        Returns:
            债券实时行情 DataFrame
        """
        cache_key = f"bond_spot:{code}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.bond_zh_hs_cov_spot(symbol=code)
            if df.empty:
                raise DataNotFoundError(f"债券 {code} 实时行情不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "债券代码": "code",
                    "债券简称": "name",
                    "最新价": "price",
                    "涨跌幅": "change_pct",
                    "涨跌额": "change",
                    "成交量": "volume",
                    "成交额": "amount",
                    "最高": "high",
                    "最低": "low",
                    "今开": "open",
                    "昨收": "prev_close",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=300)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取债券实时行情失败: {e}") from e

    def get_bond_hist(
        self,
        code: str,
        period: str = "daily",
        start_date: str | None = None,
        end_date: str | None = None,
    ) -> pd.DataFrame:
        """
        获取沪深债券历史行情

        Args:
            code: 债券代码
            period: 周期 (daily/weekly/monthly)
            start_date: 开始日期 (YYYYMMDD)
            end_date: 结束日期 (YYYYMMDD)

        Returns:
            债券历史数据 DataFrame
        """
        cache_key = f"bond_hist:{code}:{period}:{start_date}:{end_date}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.bond_zh_hs_cov_hist(
                symbol=code, period=period, start_date=start_date, end_date=end_date
            )
            if df.empty:
                raise DataNotFoundError(f"债券 {code} 历史数据不存在")

            # 标准化列名
            df = df.rename(
                columns={
                    "日期": "date",
                    "开盘": "open",
                    "收盘": "close",
                    "最高": "high",
                    "最低": "low",
                    "成交量": "volume",
                    "成交额": "amount",
                    "振幅": "amplitude",
                    "涨跌幅": "change_pct",
                    "涨跌额": "change",
                }
            )

            result = df.reset_index(drop=True)
            if self._cache:
                self._cache.set(cache_key, result, ttl=300)
            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取债券历史数据失败: {e}") from e

    # -------------------- 估值指标(5个) --------------------

    def get_a_share_valuation(self) -> pd.DataFrame:
        """获取A股等权重与中位数市盈率/市净率"""
        cache_key = "a_share_valuation"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.stock_a_pe_and_pb()
            if df is None or df.empty:
                raise DataNotFoundError("无法获取A股估值数据")
            df = self._standardize_columns(df)
            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)
            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取A股估值失败: {e}") from e

    def get_stock_valuation_lg(self, code: str) -> pd.DataFrame:
        """获取个股估值数据(乐咕乐股)"""
        cache_key = f"stock_valuation_lg:{code}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.stock_a_indicator_lg(symbol=code)
            if df is None or df.empty:
                raise DataNotFoundError(f"无法获取个股估值数据: {code}")
            df = self._standardize_columns(df)
            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)
            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取个股估值失败: {e}") from e

    def get_index_valuation(
        self, code: str, indicator: str = "pe"
    ) -> pd.DataFrame:
        """获取指数估值历史数据(乐咕乐股)"""
        cache_key = f"index_valuation:{code}:{indicator}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.index_value_hist_funddb(symbol=code, indicator=indicator)
            if df is None or df.empty:
                raise DataNotFoundError(f"无法获取指数估值数据: {code}")
            df = self._standardize_columns(df)
            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)
            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取指数估值失败: {e}") from e

    def get_market_pe_lg(self, code: str) -> pd.DataFrame:
        """获取指数市盈率数据(乐咕乐股)"""
        cache_key = f"market_pe_lg:{code}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.stock_market_pe_lg(symbol=code)
            if df is None or df.empty:
                raise DataNotFoundError(f"无法获取市场PE数据: {code}")
            df = self._standardize_columns(df)
            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)
            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取市场PE失败: {e}") from e

    def get_market_pb_lg(self, code: str) -> pd.DataFrame:
        """获取指数市净率数据(乐咕乐股)"""
        cache_key = f"market_pb_lg:{code}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.stock_market_pb_lg(symbol=code)
            if df is None or df.empty:
                raise DataNotFoundError(f"无法获取市场PB数据: {code}")
            df = self._standardize_columns(df)
            if self._cache:
                self._cache.set(cache_key, df, ttl=86400)
            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取市场PB失败: {e}") from e

    # -------------------- 资金流向(3个) --------------------

    def get_market_fund_flow(self) -> pd.DataFrame:
        """获取大盘资金流向数据"""
        cache_key = "market_fund_flow"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.stock_market_fund_flow()
            if df is None or df.empty:
                raise DataNotFoundError("无法获取大盘资金流向数据")
            df = self._standardize_columns(df)
            if self._cache:
                self._cache.set(cache_key, df, ttl=60)
            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取大盘资金流向失败: {e}") from e

    def get_stock_fund_flow(self, code: str, market: str = "sh") -> pd.DataFrame:
        """获取个股资金流向数据"""
        cache_key = f"stock_fund_flow:{code}:{market}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.stock_individual_fund_flow(stock=code, market=market)
            if df is None or df.empty:
                raise DataNotFoundError(f"无法获取个股资金流向数据: {code}")
            df = self._standardize_columns(df)
            if self._cache:
                self._cache.set(cache_key, df, ttl=60)
            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取个股资金流向失败: {e}") from e

    def get_north_fund_flow(self, market: str = "北向资金") -> pd.DataFrame:
        """获取北向资金流向数据"""
        cache_key = f"north_fund_flow:{market}"
        if self._cache and self._cache.exists(cache_key):
            return self._cache.get(cache_key)

        ak = self._get_akshare()
        try:
            df = ak.stock_hsgt_north_net_flow_in_em(symbol=market)
            if df is None or df.empty:
                raise DataNotFoundError(f"无法获取北向资金流向数据: {market}")
            df = self._standardize_columns(df)
            if self._cache:
                self._cache.set(cache_key, df, ttl=60)
            return df
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取北向资金流向失败: {e}") from e

    # ==================== P1 接口实现 ====================

    # ---------- 基金公司/规模 (5个) ----------

    def get_fund_company_aum(self) -> pd.DataFrame:
        """
        基金公司管理规模排名

        Returns:
            基金公司规模排名 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_aum_em()
            if df.empty:
                raise DataNotFoundError("基金公司规模数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金公司规模排名失败: {e}") from e

    def get_fund_aum_trend(self) -> pd.DataFrame:
        """
        基金市场管理规模走势

        Returns:
            规模走势 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_aum_trend_em()
            if df.empty:
                raise DataNotFoundError("基金规模走势数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金规模走势失败: {e}") from e

    def get_fund_company_aum_history(self, year: int | None = None) -> pd.DataFrame:
        """
        基金公司历年管理规模

        Args:
            year: 年份,如 2023

        Returns:
            历年规模数据 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_aum_hist_em(year=year)
            if df.empty:
                raise DataNotFoundError(f"{year}年基金公司规模数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金公司历年规模失败: {e}") from e

    def get_fund_scale_change(self) -> pd.DataFrame:
        """
        规模变动(全市场汇总)

        Returns:
            规模变动数据 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_scale_change_em()
            if df.empty:
                raise DataNotFoundError("基金规模变动数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金规模变动失败: {e}") from e

    def get_fund_holder_structure(self) -> pd.DataFrame:
        """
        持有人结构

        Returns:
            持有人结构数据 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_hold_structure_em()
            if df.empty:
                raise DataNotFoundError("基金持有人结构数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金持有人结构失败: {e}") from e

    # ---------- 基金评级 (4个) ----------

    def get_fund_ratings(self) -> pd.DataFrame:
        """
        基金评级总汇

        Returns:
            基金评级汇总 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_rating_all()
            if df.empty:
                raise DataNotFoundError("基金评级数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金评级总汇失败: {e}") from e

    def get_fund_rating_sh(self, date: str | None = None) -> pd.DataFrame:
        """
        上海证券评级

        Args:
            date: 日期,格式 YYYYMMDD

        Returns:
            上海证券评级数据 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_rating_sh(date=date)
            if df.empty:
                raise DataNotFoundError(f"上海证券评级数据不存在 (日期: {date})")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取上海证券评级失败: {e}") from e

    def get_fund_rating_zs(self, date: str | None = None) -> pd.DataFrame:
        """
        招商证券评级

        Args:
            date: 日期,格式 YYYYMMDD

        Returns:
            招商证券评级数据 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_rating_zs(date=date)
            if df.empty:
                raise DataNotFoundError(f"招商证券评级数据不存在 (日期: {date})")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取招商证券评级失败: {e}") from e

    def get_fund_rating_ja(self, date: str | None = None) -> pd.DataFrame:
        """
        济安金信评级

        Args:
            date: 日期,格式 YYYYMMDD

        Returns:
            济安金信评级数据 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_rating_ja(date=date)
            if df.empty:
                raise DataNotFoundError(f"济安金信评级数据不存在 (日期: {date})")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取济安金信评级失败: {e}") from e

    # ---------- 基金分红/拆分 (3个) ----------

    def get_fund_dividends(
        self,
        year: int | None = None,
        fund_type: str | None = None,
        page: int = -1,
    ) -> pd.DataFrame:
        """
        基金分红

        Args:
            year: 年份
            fund_type: 基金类型
            page: 页码,-1表示全部

        Returns:
            基金分红数据 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_fh_em(year=year, typ=fund_type, page=page)
            if df.empty:
                raise DataNotFoundError("基金分红数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金分红数据失败: {e}") from e

    def get_fund_splits(
        self,
        year: int | None = None,
        fund_type: str | None = None,
        page: int = -1,
    ) -> pd.DataFrame:
        """
        基金拆分

        Args:
            year: 年份
            fund_type: 基金类型
            page: 页码,-1表示全部

        Returns:
            基金拆分数据 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_cf_em(year=year, typ=fund_type, page=page)
            if df.empty:
                raise DataNotFoundError("基金拆分数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金拆分数据失败: {e}") from e

    def get_fund_dividend_rank(self) -> pd.DataFrame:
        """
        累计分红排行

        Returns:
            累计分红排行 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_fh_rank_em()
            if df.empty:
                raise DataNotFoundError("基金累计分红排行数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金累计分红排行失败: {e}") from e

    # ---------- 基金排行 (5个) ----------

    def get_fund_rank_by_type(self, fund_type: str = "全部") -> pd.DataFrame:
        """
        开放式基金排行

        Args:
            fund_type: 基金类型,可选:全部/股票型/混合型/债券型/指数型/QDII/LOF/FOF

        Returns:
            基金排行 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_open_fund_rank_em(symbol=fund_type)
            if df.empty:
                raise DataNotFoundError(f"{fund_type}类型基金排行数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金排行失败: {e}") from e

    def get_exchange_fund_rank(self) -> pd.DataFrame:
        """
        场内交易基金排行

        Returns:
            场内基金排行 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_exchange_rank_em()
            if df.empty:
                raise DataNotFoundError("场内交易基金排行数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取场内基金排行失败: {e}") from e

    def get_money_fund_rank(self) -> pd.DataFrame:
        """
        货币型基金排行

        Returns:
            货币基金排行 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_money_rank_em()
            if df.empty:
                raise DataNotFoundError("货币型基金排行数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取货币基金排行失败: {e}") from e

    def get_lcx_fund_rank(self) -> pd.DataFrame:
        """
        理财基金排行

        Returns:
            理财基金排行 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_lcx_rank_em()
            if df.empty:
                raise DataNotFoundError("理财基金排行数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取理财基金排行失败: {e}") from e

    def get_hk_fund_rank(self) -> pd.DataFrame:
        """
        香港基金排行

        Returns:
            香港基金排行 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_hk_rank_em()
            if df.empty:
                raise DataNotFoundError("香港基金排行数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取香港基金排行失败: {e}") from e

    # ---------- 基金业绩/分析 (3个) ----------

    def get_fund_achievement(self, code: str) -> pd.DataFrame:
        """
        基金业绩(年度+阶段)

        Args:
            code: 基金代码

        Returns:
            基金业绩数据 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_individual_achievement_xq(symbol=code)
            if df.empty:
                raise DataNotFoundError(f"基金 {code} 业绩数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金业绩失败: {e}") from e

    def get_fund_risk_analysis(self, code: str) -> pd.DataFrame:
        """
        基金数据分析(夏普/回撤)

        Args:
            code: 基金代码

        Returns:
            基金风险分析数据 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_individual_analysis_xq(symbol=code)
            if df.empty:
                raise DataNotFoundError(f"基金 {code} 风险分析数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金风险分析失败: {e}") from e

    def get_fund_profit_probability(self, code: str) -> pd.DataFrame:
        """
        盈利概率

        Args:
            code: 基金代码

        Returns:
            盈利概率数据 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_individual_profit_probability_xq(symbol=code)
            if df.empty:
                raise DataNotFoundError(f"基金 {code} 盈利概率数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金盈利概率失败: {e}") from e

    # ---------- 资产配置 (1个) ----------

    def get_fund_asset_allocation(
        self, code: str, date: str | None = None
    ) -> pd.DataFrame:
        """
        基金资产配置

        Args:
            code: 基金代码
            date: 财报日期,格式 YYYYMMDD

        Returns:
            资产配置数据 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.fund_individual_detail_hold_xq(symbol=code, date=date)
            if df.empty:
                raise DataNotFoundError(f"基金 {code} 资产配置数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金资产配置失败: {e}") from e

    # ---------- 市场指数扩展 (6个) ----------

    def get_index_spot_em(self, category: str = "沪深重要指数") -> pd.DataFrame:
        """
        东财指数实时行情

        Args:
            category: 指数类别,可选:沪深重要指数/上证系列指数/深证系列指数/中证系列指数

        Returns:
            指数实时行情 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.stock_zh_index_spot_em(symbol=category)
            if df.empty:
                raise DataNotFoundError(f"{category}指数实时行情数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取东财指数实时行情失败: {e}") from e

    def get_index_spot_sina(self) -> pd.DataFrame:
        """
        新浪指数实时行情

        Returns:
            指数实时行情 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.stock_zh_index_spot_sina()
            if df.empty:
                raise DataNotFoundError("新浪指数实时行情数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取新浪指数实时行情失败: {e}") from e

    def get_index_daily_tx(
        self, code: str, start: str | None = None, end: str | None = None
    ) -> pd.DataFrame:
        """
        腾讯指数历史

        Args:
            code: 指数代码,如 "sh000001"
            start: 开始日期,格式 YYYYMMDD
            end: 结束日期,格式 YYYYMMDD

        Returns:
            指数历史数据 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.stock_zh_index_daily_tx(symbol=code, start_date=start, end_date=end)
            if df.empty:
                raise DataNotFoundError(f"指数 {code} 历史数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取腾讯指数历史失败: {e}") from e

    def get_index_daily_em(
        self, code: str, start: str | None = None, end: str | None = None
    ) -> pd.DataFrame:
        """
        东财指数历史

        Args:
            code: 指数代码,如 "sz399552"
            start: 开始日期,格式 YYYYMMDD
            end: 结束日期,格式 YYYYMMDD

        Returns:
            指数历史数据 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.stock_zh_index_daily_em(symbol=code, start_date=start, end_date=end)
            if df.empty:
                raise DataNotFoundError(f"指数 {code} 历史数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取东财指数历史失败: {e}") from e

    def get_index_hist(
        self,
        code: str,
        period: str = "daily",
        start: str | None = None,
        end: str | None = None,
    ) -> pd.DataFrame:
        """
        指数通用历史

        Args:
            code: 指数代码
            period: 周期,可选:daily/weekly/monthly
            start: 开始日期,格式 YYYYMMDD
            end: 结束日期,格式 YYYYMMDD

        Returns:
            指数历史数据 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.index_zh_a_hist(
                symbol=code, period=period, start_date=start, end_date=end
            )
            if df.empty:
                raise DataNotFoundError(f"指数 {code} 历史数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取指数通用历史失败: {e}") from e

    def get_index_minute(
        self,
        code: str,
        period: str = "1",
        start: str | None = None,
        end: str | None = None,
    ) -> pd.DataFrame:
        """
        指数分时

        Args:
            code: 指数代码
            period: 周期,可选:1/5/15/30/60
            start: 开始日期,格式 YYYYMMDD
            end: 结束日期,格式 YYYYMMDD

        Returns:
            指数分时数据 DataFrame
        """
        ak = self._get_akshare()
        try:
            df = ak.index_zh_a_hist_min_em(
                symbol=code, period=period, start_date=start, end_date=end
            )
            if df.empty:
                raise DataNotFoundError(f"指数 {code} 分时数据不存在")
            return df.reset_index(drop=True)
        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取指数分时数据失败: {e}") from e

__init__

__init__(cache: DataCache | None = None)

初始化 AKShare 适配器

参数:

名称 类型 描述 默认
cache DataCache | None

缓存管理器,可选

None
源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def __init__(self, cache: DataCache | None = None):
    """
    初始化 AKShare 适配器

    Args:
        cache: 缓存管理器,可选
    """
    super().__init__("akshare")
    self._cache = cache
    self._ak = None

is_available

is_available() -> bool

检查 AKShare 是否可用

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def is_available(self) -> bool:
    """检查 AKShare 是否可用"""
    try:
        self._get_akshare()
        return True
    except Exception:
        return False

get_fund_info

get_fund_info(fund_code: str) -> dict[str, Any]

获取基金基础信息

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需

返回:

类型 描述
dict[str, Any]

基金信息字典

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_info(self, fund_code: str) -> dict[str, Any]:
    """
    获取基金基础信息

    Args:
        fund_code: 基金代码

    Returns:
        基金信息字典
    """
    # 检查缓存
    if self._cache:
        cached = self._cache.get_fund_info(fund_code)
        if cached:
            return cached

    ak = self._get_akshare()

    try:
        # 获取基金基本信息
        df = ak.fund_individual_basic_info_xq(symbol=fund_code)

        if df.empty:
            raise DataNotFoundError(f"基金 {fund_code} 不存在")

        # 解析数据
        info_dict = dict(zip(df["item"], df["value"], strict=False))

        result = {
            "code": fund_code,
            "name": info_dict.get("基金简称", info_dict.get("基金全称", "")),
            "type": info_dict.get("基金类型", "未知"),
            "establish_date": self._parse_date(info_dict.get("成立日期")),
            "manager": info_dict.get("基金经理", ""),
            "company": info_dict.get("基金管理人", ""),
            "scale": self._parse_scale(info_dict.get("基金规模")),
        }

        # 缓存结果
        if self._cache:
            self._cache.set_fund_info(fund_code, result)

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金信息失败: {e}") from e

get_fund_nav

get_fund_nav(
    fund_code: str,
    start_date: date | None = None,
    end_date: date | None = None,
) -> pd.DataFrame

获取基金净值数据

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需
start_date date | None

开始日期

None
end_date date | None

结束日期

None

返回:

类型 描述
DataFrame

净值数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_nav(
    self,
    fund_code: str,
    start_date: date | None = None,
    end_date: date | None = None,
) -> pd.DataFrame:
    """
    获取基金净值数据

    Args:
        fund_code: 基金代码
        start_date: 开始日期
        end_date: 结束日期

    Returns:
        净值数据 DataFrame
    """
    # 格式化日期
    start_str = start_date.strftime("%Y%m%d") if start_date else "19900101"
    end_str = end_date.strftime("%Y%m%d") if end_date else datetime.now().strftime("%Y%m%d")

    # 检查缓存
    if self._cache:
        cached = self._cache.get_fund_nav(fund_code, start_str, end_str)
        if cached is not None:
            return cached

    ak = self._get_akshare()

    try:
        # 获取开放式基金净值数据
        df = ak.fund_open_fund_info_em(fund=fund_code, indicator="单位净值走势")

        if df.empty:
            raise DataNotFoundError(f"基金 {fund_code} 净值数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "净值日期": "nav_date",
                "单位净值": "unit_nav",
                "日增长率": "daily_return",
            }
        )

        # 处理日期
        df["nav_date"] = pd.to_datetime(df["nav_date"])

        # 筛选日期范围
        if start_date:
            df = df[df["nav_date"] >= pd.Timestamp(start_date)]
        if end_date:
            df = df[df["nav_date"] <= pd.Timestamp(end_date)]

        # 添加基金代码
        df["fund_code"] = fund_code

        # 计算累计净值(如果没有)
        if "accumulated_nav" not in df.columns:
            df["accumulated_nav"] = df["unit_nav"]

        # 选择输出列
        result_df = df[
            ["fund_code", "nav_date", "unit_nav", "accumulated_nav", "daily_return"]
        ].copy()
        result_df = result_df.sort_values("nav_date").reset_index(drop=True)

        # 缓存结果
        if self._cache:
            self._cache.set_fund_nav(fund_code, start_str, end_str, result_df)

        return result_df

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金净值失败: {e}") from e

search_funds

search_funds(
    fund_type: str | None = None,
    company: str | None = None,
    min_scale: float | None = None,
    max_scale: float | None = None,
    keyword: str | None = None,
    limit: int = 100,
) -> pd.DataFrame

搜索/筛选基金

参数:

名称 类型 描述 默认
fund_type str | None

基金类型

None
company str | None

基金公司

None
min_scale float | None

最小规模

None
max_scale float | None

最大规模

None
keyword str | None

关键词

None
limit int

返回数量限制

100

返回:

类型 描述
DataFrame

基金列表 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def search_funds(
    self,
    fund_type: str | None = None,
    company: str | None = None,
    min_scale: float | None = None,
    max_scale: float | None = None,
    keyword: str | None = None,
    limit: int = 100,
) -> pd.DataFrame:
    """
    搜索/筛选基金

    Args:
        fund_type: 基金类型
        company: 基金公司
        min_scale: 最小规模
        max_scale: 最大规模
        keyword: 关键词
        limit: 返回数量限制

    Returns:
        基金列表 DataFrame
    """
    ak = self._get_akshare()

    try:
        # 获取全部开放式基金列表
        df = ak.fund_open_fund_daily_em()

        # 筛选条件
        if fund_type:
            df = df[df["基金类型"].str.contains(fund_type, na=False)]

        if company:
            df = df[df["基金公司"].str.contains(company, na=False)]

        if keyword:
            mask = df["基金代码"].str.contains(keyword, na=False) | df["基金简称"].str.contains(
                keyword, na=False
            )
            df = df[mask]

        # 规模筛选
        if min_scale is not None:
            df = df[df["基金规模"] >= min_scale]
        if max_scale is not None:
            df = df[df["基金规模"] <= max_scale]

        # 限制返回数量
        df = df.head(limit)

        # 标准化列名
        df = df.rename(
            columns={
                "基金代码": "code",
                "基金简称": "name",
                "基金类型": "type",
                "基金规模": "scale",
                "基金公司": "company",
            }
        )

        return df.reset_index(drop=True)

    except Exception as e:
        raise DataSourceError(f"搜索基金失败: {e}") from e

get_fund_list

get_fund_list(fund_type: str | None = None) -> pd.DataFrame

获取基金列表

参数:

名称 类型 描述 默认
fund_type str | None

基金类型筛选

None

返回:

类型 描述
DataFrame

基金列表 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_list(self, fund_type: str | None = None) -> pd.DataFrame:
    """
    获取基金列表

    Args:
        fund_type: 基金类型筛选

    Returns:
        基金列表 DataFrame
    """
    return self.search_funds(fund_type=fund_type)

get_benchmark_nav

get_benchmark_nav(
    benchmark_code: str,
    start_date: date | None = None,
    end_date: date | None = None,
) -> pd.DataFrame

获取基准指数数据

参数:

名称 类型 描述 默认
benchmark_code str

基准指数代码

必需
start_date date | None

开始日期

None
end_date date | None

结束日期

None

返回:

类型 描述
DataFrame

基准数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_benchmark_nav(
    self,
    benchmark_code: str,
    start_date: date | None = None,
    end_date: date | None = None,
) -> pd.DataFrame:
    """
    获取基准指数数据

    Args:
        benchmark_code: 基准指数代码
        start_date: 开始日期
        end_date: 结束日期

    Returns:
        基准数据 DataFrame
    """
    ak = self._get_akshare()

    try:
        # 获取指数数据
        df = ak.stock_zh_index_daily(symbol=f"sh{benchmark_code}")

        if df.empty:
            raise DataNotFoundError(f"指数 {benchmark_code} 不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "date": "nav_date",
                "close": "unit_nav",
            }
        )

        # 处理日期
        df["nav_date"] = pd.to_datetime(df["nav_date"])

        # 筛选日期范围
        if start_date:
            df = df[df["nav_date"] >= pd.Timestamp(start_date)]
        if end_date:
            df = df[df["nav_date"] <= pd.Timestamp(end_date)]

        # 计算收益率
        df["daily_return"] = df["unit_nav"].pct_change() * 100
        df["fund_code"] = benchmark_code
        df["accumulated_nav"] = df["unit_nav"]

        result_df = df[
            ["fund_code", "nav_date", "unit_nav", "accumulated_nav", "daily_return"]
        ].copy()
        return result_df.sort_values("nav_date").reset_index(drop=True)

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基准数据失败: {e}") from e

get_fund_holdings

get_fund_holdings(
    fund_code: str, report_date: date | None = None
) -> pd.DataFrame

获取基金持仓数据

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_holdings(
    self,
    fund_code: str,
    report_date: date | None = None,
) -> pd.DataFrame:
    """获取基金持仓数据"""
    cache_key = f"holdings:{fund_code}:{report_date or 'latest'}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.fund_portfolio_hold_em(symbol=fund_code, date=report_date)
        if df.empty:
            raise DataNotFoundError(f"基金 {fund_code} 持仓数据不存在")
        df = df.rename(
            columns={
                "季度": "report_date",
                "股票代码": "stock_code",
                "股票名称": "stock_name",
                "占净值比例": "weight",
                "持股数": "holdings_count",
                "持仓市值": "market_value",
            }
        )
        if "fund_code" not in df.columns:
            df["fund_code"] = fund_code
        result = df.sort_values("weight", ascending=False).reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=86400)
        return result
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取持仓数据失败: {e}") from e

get_fund_manager

get_fund_manager(fund_code: str) -> dict[str, Any]

获取基金经理信息

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_manager(self, fund_code: str) -> dict[str, Any]:
    """获取基金经理信息"""
    info = self.get_fund_info(fund_code)
    return {
        "name": info.get("manager", ""),
        "fund_code": fund_code,
        "fund_name": info.get("name", ""),
        "company": info.get("company", ""),
    }

get_fund_fee

get_fund_fee(fund_code: str) -> dict[str, Any]

获取基金费率信息

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_fee(self, fund_code: str) -> dict[str, Any]:
    """获取基金费率信息"""
    ak = self._get_akshare()
    try:
        df = ak.fund_individual_detail_info_xq(symbol=fund_code)
        if df.empty:
            raise DataNotFoundError(f"基金 {fund_code} 费率信息不存在")
        info_dict = dict(zip(df["item"], df["value"], strict=False))
        return {
            "management_fee": info_dict.get("管理费率", ""),
            "custody_fee": info_dict.get("托管费率", ""),
            "purchase_fee": info_dict.get("申购费率", ""),
            "redeem_fee": info_dict.get("赎回费率", ""),
        }
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取费率信息失败: {e}") from e

get_fund_rating

get_fund_rating(fund_code: str) -> int | None

获取基金评级

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_rating(self, fund_code: str) -> int | None:
    """获取基金评级"""
    ak = self._get_akshare()
    try:
        df = ak.fund_individual_detail_info_xq(symbol=fund_code)
        if df.empty:
            return None
        info_dict = dict(zip(df["item"], df["value"], strict=False))
        rating_str = info_dict.get("基金评级", "")
        if not rating_str:
            return None
        import re

        match = re.search(r"(\d+)", str(rating_str))
        return int(match.group(1)) if match else None
    except Exception:
        return None

batch_get_fund_nav

batch_get_fund_nav(
    fund_codes: list[str],
    start_date: date | None = None,
    end_date: date | None = None,
) -> dict[str, pd.DataFrame]

批量获取基金净值数据

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def batch_get_fund_nav(
    self,
    fund_codes: list[str],
    start_date: date | None = None,
    end_date: date | None = None,
) -> dict[str, pd.DataFrame]:
    """批量获取基金净值数据"""
    results = {}
    for code in fund_codes:
        try:
            results[code] = self.get_fund_nav(code, start_date, end_date)
        except Exception:
            results[code] = pd.DataFrame()
    return results

get_all_fund_names

get_all_fund_names() -> pd.DataFrame

获取所有基金名称列表

返回:

名称 类型 描述
DataFrame

基金名称列表 DataFrame

DataFrame

code, pinyin_abbr, name, type, pinyin_full

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_all_fund_names(self) -> pd.DataFrame:
    """
    获取所有基金名称列表

    Returns:
        基金名称列表 DataFrame
        列: code, pinyin_abbr, name, type, pinyin_full
    """
    cache_key = "fund_all_names"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.fund_name_em()
        if df.empty:
            raise DataNotFoundError("无法获取基金名称列表")

        # 标准化列名
        df = df.rename(
            columns={
                "基金代码": "code",
                "拼音缩写": "pinyin_abbr",
                "基金简称": "name",
                "基金类型": "type",
                "拼音全称": "pinyin_full",
            }
        )

        result = df.reset_index(drop=True)

        # 缓存结果
        if self._cache:
            self._cache.set(cache_key, result, ttl=86400)  # 缓存1天

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金名称列表失败: {e}") from e

get_fund_info_ths

get_fund_info_ths(code: str) -> dict[str, Any]

获取同花顺基金基本信息

参数:

名称 类型 描述 默认
code str

基金代码

必需

返回:

类型 描述
dict[str, Any]

基金基本信息字典

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_info_ths(self, code: str) -> dict[str, Any]:
    """
    获取同花顺基金基本信息

    Args:
        code: 基金代码

    Returns:
        基金基本信息字典
    """
    cache_key = f"fund_info_ths:{code}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)  # type: ignore[return-value]

    ak = self._get_akshare()
    try:
        df = ak.fund_info_ths(symbol=code)
        if df.empty:
            raise DataNotFoundError(f"基金 {code} 不存在")

        # 转换为字典
        info_dict = dict(zip(df["字段"], df["值"], strict=False))

        result = {
            "code": code,
            "name": info_dict.get("基金简称", ""),
            "full_name": info_dict.get("基金全称", ""),
            "type": info_dict.get("基金类型", ""),
            "investment_type": info_dict.get("投资类型", ""),
            "manager": info_dict.get("基金经理", ""),
            "establish_date": self._parse_date(info_dict.get("成立日期")),
            "establish_scale": info_dict.get("成立规模", ""),
            "management_fee": info_dict.get("管理费", ""),
            "share_scale": info_dict.get("份额规模", ""),
            "custody_fee": info_dict.get("托管费", ""),
            "management_company": info_dict.get("基金管理人", ""),
            "custodian": info_dict.get("基金托管人", ""),
            "subscription_fee": info_dict.get("认购费", ""),
            "purchase_fee": info_dict.get("申购费", ""),
            "redemption_fee": info_dict.get("赎回费", ""),
            "benchmark": info_dict.get("业绩比较基准", ""),
        }

        # 缓存结果
        if self._cache:
            self._cache.set(cache_key, result, ttl=86400)

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取同花顺基金信息失败: {e}") from e

get_index_fund_info

get_index_fund_info(
    category: str = "全部", indicator: str = "全部"
) -> pd.DataFrame

获取指数型基金基本信息

参数:

名称 类型 描述 默认
category str

分类,可选 {"全部","沪深指数","行业主题","大盘指数","中盘指数","小盘指数","股票指数","债券指数"}

'全部'
indicator str

类型,可选 {"全部","被动指数型","增强指数型"}

'全部'

返回:

类型 描述
DataFrame

指数型基金信息 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_index_fund_info(
    self,
    category: str = "全部",
    indicator: str = "全部",
) -> pd.DataFrame:
    """
    获取指数型基金基本信息

    Args:
        category: 分类,可选 {"全部","沪深指数","行业主题","大盘指数","中盘指数","小盘指数","股票指数","债券指数"}
        indicator: 类型,可选 {"全部","被动指数型","增强指数型"}

    Returns:
        指数型基金信息 DataFrame
    """
    cache_key = f"index_fund_info:{category}:{indicator}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.fund_info_index_em(symbol=category, indicator=indicator)
        if df.empty:
            raise DataNotFoundError("无法获取指数型基金信息")

        # 标准化列名
        df = df.rename(
            columns={
                "基金代码": "code",
                "基金名称": "name",
                "单位净值": "unit_nav",
                "日期": "nav_date",
                "日增长率": "daily_return",
                "近1周": "return_1w",
                "近1月": "return_1m",
                "近3月": "return_3m",
                "近6月": "return_6m",
                "近1年": "return_1y",
                "近2年": "return_2y",
                "近3年": "return_3y",
                "今年来": "return_ytd",
                "成立来": "return_since_inception",
                "手续费": "fee",
                "起购金额": "min_purchase",
                "跟踪标的": "tracking_target",
                "跟踪方式": "tracking_method",
            }
        )

        result = df.reset_index(drop=True)

        # 缓存结果
        if self._cache:
            self._cache.set(cache_key, result, ttl=3600)  # 缓存1小时

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取指数型基金信息失败: {e}") from e

get_fund_overview

get_fund_overview(code: str) -> dict[str, Any]

获取基金档案基本概况

参数:

名称 类型 描述 默认
code str

基金代码

必需

返回:

类型 描述
dict[str, Any]

基金概况字典

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_overview(self, code: str) -> dict[str, Any]:
    """
    获取基金档案基本概况

    Args:
        code: 基金代码

    Returns:
        基金概况字典
    """
    cache_key = f"fund_overview:{code}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)  # type: ignore[return-value]

    ak = self._get_akshare()
    try:
        df = ak.fund_overview_em(symbol=code)
        if df.empty:
            raise DataNotFoundError(f"基金 {code} 概况不存在")

        # 转换为字典
        result = dict(zip(df["Key"], df["Value"], strict=False))
        result["code"] = code

        # 缓存结果
        if self._cache:
            self._cache.set(cache_key, result, ttl=86400)

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金概况失败: {e}") from e

get_fund_purchase_status

get_fund_purchase_status() -> pd.DataFrame

获取基金申购/赎回状态

返回:

类型 描述
DataFrame

基金申购赎回状态 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_purchase_status(self) -> pd.DataFrame:
    """
    获取基金申购/赎回状态

    Returns:
        基金申购赎回状态 DataFrame
    """
    cache_key = "fund_purchase_status"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.fund_purchase_em()
        if df.empty:
            raise DataNotFoundError("无法获取基金申购赎回状态")

        # 标准化列名
        df = df.rename(
            columns={
                "基金代码": "code",
                "基金简称": "name",
                "基金类型": "type",
                "最新净值/万份收益": "latest_nav",
                "报告时间": "report_time",
                "申购状态": "purchase_status",
                "赎回状态": "redemption_status",
                "下一开放日": "next_open_day",
                "购买起点": "min_purchase",
                "日累计限定金额": "daily_limit",
                "手续费": "fee",
            }
        )

        result = df.reset_index(drop=True)

        # 缓存结果(短期缓存,因为状态可能变化)
        if self._cache:
            self._cache.set(cache_key, result, ttl=1800)  # 缓存30分钟

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金申购赎回状态失败: {e}") from e

get_fund_daily_nav

get_fund_daily_nav() -> pd.DataFrame

获取开放式基金每日净值(全部)

返回:

类型 描述
DataFrame

每日净值 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_daily_nav(self) -> pd.DataFrame:
    """
    获取开放式基金每日净值(全部)

    Returns:
        每日净值 DataFrame
    """
    cache_key = "fund_daily_nav"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.fund_open_fund_daily_em()
        if df.empty:
            raise DataNotFoundError("无法获取基金每日净值")

        # 标准化列名
        df = df.rename(
            columns={
                "基金代码": "code",
                "基金简称": "name",
                "当日单位净值": "unit_nav",
                "当日累计净值": "accumulated_nav",
                "前日单位净值": "prev_unit_nav",
                "前日累计净值": "prev_accumulated_nav",
                "日增长值": "daily_change",
                "日增长率": "daily_return",
                "申购状态": "purchase_status",
                "赎回状态": "redemption_status",
                "手续费": "fee",
            }
        )

        result = df.reset_index(drop=True)

        # 缓存结果
        if self._cache:
            self._cache.set(cache_key, result, ttl=3600)  # 缓存1小时

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金每日净值失败: {e}") from e

get_etf_spot

get_etf_spot() -> pd.DataFrame

获取ETF实时行情

返回:

类型 描述
DataFrame

ETF实时行情 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_etf_spot(self) -> pd.DataFrame:
    """
    获取ETF实时行情

    Returns:
        ETF实时行情 DataFrame
    """
    cache_key = "etf_spot"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.fund_etf_spot_em()
        if df.empty:
            raise DataNotFoundError("无法获取ETF实时行情")

        # 标准化列名(转换为snake_case)
        column_mapping = {
            "代码": "code",
            "名称": "name",
            "最新价": "latest_price",
            "IOPV实时估值": "iopv",
            "基金折价率": "discount_rate",
            "涨跌额": "change_amount",
            "涨跌幅": "change_pct",
            "成交量": "volume",
            "成交额": "turnover",
            "开盘价": "open",
            "最高价": "high",
            "最低价": "low",
            "昨收": "prev_close",
            "换手率": "turnover_rate",
            "量比": "volume_ratio",
            "委比": "order_ratio",
            "外盘": "outer_volume",
            "内盘": "inner_volume",
            "主力净流入-净额": "main_net_inflow",
            "主力净流入-净占比": "main_net_inflow_pct",
            "超大单净流入-净额": "super_large_net_inflow",
            "超大单净流入-净占比": "super_large_net_inflow_pct",
            "大单净流入-净额": "large_net_inflow",
            "大单净流入-净占比": "large_net_inflow_pct",
            "中单净流入-净额": "medium_net_inflow",
            "中单净流入-净占比": "medium_net_inflow_pct",
            "小单净流入-净额": "small_net_inflow",
            "小单净流入-净占比": "small_net_inflow_pct",
            "现手": "current_hand",
            "买一": "bid1",
            "卖一": "ask1",
            "最新份额": "latest_shares",
            "流通市值": "circulating_market_cap",
            "总市值": "total_market_cap",
            "数据日期": "data_date",
            "更新时间": "update_time",
        }

        df = df.rename(columns=column_mapping)
        result = df.reset_index(drop=True)

        # 缓存结果(短期缓存)
        if self._cache:
            self._cache.set(cache_key, result, ttl=300)  # 缓存5分钟

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取ETF实时行情失败: {e}") from e

get_fund_category_spot

get_fund_category_spot(
    category: str = "", date: str | None = None
) -> pd.DataFrame

获取同花顺基金实时行情(按类型)

参数:

名称 类型 描述 默认
category str

基金类型,可选 {"股票型","债券型","混合型","ETF","LOF","QDII","保本型","指数型",""}

''
date str | None

日期,格式 YYYYMMDD,默认为今天

None

返回:

类型 描述
DataFrame

基金实时行情 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_category_spot(
    self,
    category: str = "",
    date: str | None = None,
) -> pd.DataFrame:
    """
    获取同花顺基金实时行情(按类型)

    Args:
        category: 基金类型,可选 {"股票型","债券型","混合型","ETF","LOF","QDII","保本型","指数型",""}
        date: 日期,格式 YYYYMMDD,默认为今天

    Returns:
        基金实时行情 DataFrame
    """
    if date is None:
        date = datetime.now().strftime("%Y%m%d")

    cache_key = f"fund_category_spot:{category}:{date}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.fund_etf_category_ths(symbol=category, date=date)
        if df.empty:
            raise DataNotFoundError(f"无法获取 {category} 类型基金实时行情")

        # 标准化列名
        df = df.rename(
            columns={
                "基金代码": "code",
                "基金名称": "name",
                "当前单位净值": "current_unit_nav",
                "当前累计净值": "current_accumulated_nav",
                "前一日单位净值": "prev_unit_nav",
                "前一日累计净值": "prev_accumulated_nav",
                "增长值": "change_value",
                "增长率": "change_rate",
                "赎回状态": "redemption_status",
                "申购状态": "purchase_status",
                "最新交易日": "latest_trade_date",
                "最新单位净值": "latest_unit_nav",
                "最新累计净值": "latest_accumulated_nav",
                "基金类型": "type",
                "查询日期": "query_date",
            }
        )

        result = df.reset_index(drop=True)

        # 缓存结果
        if self._cache:
            self._cache.set(cache_key, result, ttl=1800)  # 缓存30分钟

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金分类实时行情失败: {e}") from e

get_etf_spot_ths

get_etf_spot_ths(date: str | None = None) -> pd.DataFrame

获取同花顺ETF实时行情

参数:

名称 类型 描述 默认
date str | None

日期,格式 YYYYMMDD,默认为今天

None

返回:

类型 描述
DataFrame

ETF实时行情 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_etf_spot_ths(self, date: str | None = None) -> pd.DataFrame:
    """
    获取同花顺ETF实时行情

    Args:
        date: 日期,格式 YYYYMMDD,默认为今天

    Returns:
        ETF实时行情 DataFrame
    """
    if date is None:
        date = datetime.now().strftime("%Y%m%d")

    cache_key = f"etf_spot_ths:{date}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.fund_etf_spot_ths(date=date)
        if df.empty:
            raise DataNotFoundError("无法获取同花顺ETF实时行情")

        # 标准化列名
        df = df.rename(
            columns={
                "基金代码": "code",
                "基金名称": "name",
                "当前单位净值": "current_unit_nav",
                "当前累计净值": "current_accumulated_nav",
                "前一日单位净值": "prev_unit_nav",
                "前一日累计净值": "prev_accumulated_nav",
                "增长值": "change_value",
                "增长率": "change_rate",
                "赎回状态": "redemption_status",
                "申购状态": "purchase_status",
                "最新交易日": "latest_trade_date",
                "最新单位净值": "latest_unit_nav",
                "最新累计净值": "latest_accumulated_nav",
                "基金类型": "type",
                "查询日期": "query_date",
            }
        )

        result = df.reset_index(drop=True)

        # 缓存结果
        if self._cache:
            self._cache.set(cache_key, result, ttl=1800)  # 缓存30分钟

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取同花顺ETF实时行情失败: {e}") from e

get_lof_spot

get_lof_spot() -> pd.DataFrame

获取LOF实时行情

返回:

类型 描述
DataFrame

LOF实时行情 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_lof_spot(self) -> pd.DataFrame:
    """
    获取LOF实时行情

    Returns:
        LOF实时行情 DataFrame
    """
    cache_key = "lof_spot"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.fund_lof_spot_em()
        if df.empty:
            raise DataNotFoundError("无法获取LOF实时行情")

        # 标准化列名
        df = df.rename(
            columns={
                "代码": "code",
                "名称": "name",
                "最新价": "latest_price",
                "涨跌额": "change_amount",
                "涨跌幅": "change_pct",
                "成交量": "volume",
                "成交额": "turnover",
                "开盘价": "open",
                "最高价": "high",
                "最低价": "low",
                "昨收": "prev_close",
                "换手率": "turnover_rate",
                "流通市值": "circulating_market_cap",
                "总市值": "total_market_cap",
            }
        )

        result = df.reset_index(drop=True)

        # 缓存结果
        if self._cache:
            self._cache.set(cache_key, result, ttl=300)  # 缓存5分钟

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取LOF实时行情失败: {e}") from e

get_etf_hist

get_etf_hist(
    code: str,
    period: str = "daily",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame

获取ETF历史行情

参数:

名称 类型 描述 默认
code str

ETF代码

必需
period str

周期,可选 {"daily","weekly","monthly"}

'daily'
start str | None

开始日期,格式 YYYYMMDD

None
end str | None

结束日期,格式 YYYYMMDD

None

返回:

类型 描述
DataFrame

ETF历史行情 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_etf_hist(
    self,
    code: str,
    period: str = "daily",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame:
    """
    获取ETF历史行情

    Args:
        code: ETF代码
        period: 周期,可选 {"daily","weekly","monthly"}
        start: 开始日期,格式 YYYYMMDD
        end: 结束日期,格式 YYYYMMDD

    Returns:
        ETF历史行情 DataFrame
    """
    if end is None:
        end = datetime.now().strftime("%Y%m%d")
    if start is None:
        # 默认获取1年数据
        from datetime import timedelta
        start = (datetime.now() - timedelta(days=365)).strftime("%Y%m%d")

    cache_key = f"etf_hist:{code}:{period}:{start}:{end}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.fund_etf_hist_em(
            symbol=code,
            period=period,
            start_date=start,
            end_date=end,
        )
        if df.empty:
            raise DataNotFoundError(f"ETF {code} 历史行情不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "日期": "date",
                "开盘": "open",
                "收盘": "close",
                "最高": "high",
                "最低": "low",
                "成交量": "volume",
                "成交额": "turnover",
                "振幅": "amplitude",
                "涨跌幅": "change_pct",
                "涨跌额": "change_amount",
                "换手率": "turnover_rate",
            }
        )

        df["code"] = code
        result = df.reset_index(drop=True)

        # 缓存结果
        if self._cache:
            self._cache.set(cache_key, result, ttl=86400)  # 缓存1天

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取ETF历史行情失败: {e}") from e

get_lof_hist

get_lof_hist(
    code: str,
    period: str = "daily",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame

获取LOF历史行情

参数:

名称 类型 描述 默认
code str

LOF代码

必需
period str

周期,可选 {"daily","weekly","monthly"}

'daily'
start str | None

开始日期,格式 YYYYMMDD

None
end str | None

结束日期,格式 YYYYMMDD

None

返回:

类型 描述
DataFrame

LOF历史行情 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_lof_hist(
    self,
    code: str,
    period: str = "daily",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame:
    """
    获取LOF历史行情

    Args:
        code: LOF代码
        period: 周期,可选 {"daily","weekly","monthly"}
        start: 开始日期,格式 YYYYMMDD
        end: 结束日期,格式 YYYYMMDD

    Returns:
        LOF历史行情 DataFrame
    """
    if end is None:
        end = datetime.now().strftime("%Y%m%d")
    if start is None:
        from datetime import timedelta
        start = (datetime.now() - timedelta(days=365)).strftime("%Y%m%d")

    cache_key = f"lof_hist:{code}:{period}:{start}:{end}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.fund_lof_hist_em(
            symbol=code,
            period=period,
            start_date=start,
            end_date=end,
        )
        if df.empty:
            raise DataNotFoundError(f"LOF {code} 历史行情不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "日期": "date",
                "开盘": "open",
                "收盘": "close",
                "最高": "high",
                "最低": "low",
                "成交量": "volume",
                "成交额": "turnover",
                "振幅": "amplitude",
                "涨跌幅": "change_pct",
                "涨跌额": "change_amount",
                "换手率": "turnover_rate",
            }
        )

        df["code"] = code
        result = df.reset_index(drop=True)

        # 缓存结果
        if self._cache:
            self._cache.set(cache_key, result, ttl=86400)

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取LOF历史行情失败: {e}") from e

get_etf_minute

get_etf_minute(
    code: str,
    period: str = "1",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame

获取ETF分时行情

参数:

名称 类型 描述 默认
code str

ETF代码

必需
period str

分钟周期,可选 {"1","5","15","30","60"}

'1'
start str | None

开始日期,格式 YYYYMMDD

None
end str | None

结束日期,格式 YYYYMMDD

None

返回:

类型 描述
DataFrame

ETF分时行情 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_etf_minute(
    self,
    code: str,
    period: str = "1",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame:
    """
    获取ETF分时行情

    Args:
        code: ETF代码
        period: 分钟周期,可选 {"1","5","15","30","60"}
        start: 开始日期,格式 YYYYMMDD
        end: 结束日期,格式 YYYYMMDD

    Returns:
        ETF分时行情 DataFrame
    """
    if end is None:
        end = datetime.now().strftime("%Y%m%d")
    if start is None:
        start = datetime.now().strftime("%Y%m%d")

    cache_key = f"etf_minute:{code}:{period}:{start}:{end}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.fund_etf_hist_min_em(
            symbol=code,
            period=period,
            start_date=start,
            end_date=end,
        )
        if df.empty:
            raise DataNotFoundError(f"ETF {code} 分时行情不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "时间": "time",
                "开盘": "open",
                "收盘": "close",
                "最高": "high",
                "最低": "low",
                "成交量": "volume",
                "成交额": "turnover",
                "均价": "avg_price",
            }
        )

        df["code"] = code
        result = df.reset_index(drop=True)

        # 缓存结果(短期缓存)
        if self._cache:
            self._cache.set(cache_key, result, ttl=300)  # 缓存5分钟

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取ETF分时行情失败: {e}") from e

get_lof_minute

get_lof_minute(
    code: str,
    period: str = "1",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame

获取LOF分时行情

参数:

名称 类型 描述 默认
code str

LOF代码

必需
period str

分钟周期,可选 {"1","5","15","30","60"}

'1'
start str | None

开始日期,格式 YYYYMMDD

None
end str | None

结束日期,格式 YYYYMMDD

None

返回:

类型 描述
DataFrame

LOF分时行情 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_lof_minute(
    self,
    code: str,
    period: str = "1",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame:
    """
    获取LOF分时行情

    Args:
        code: LOF代码
        period: 分钟周期,可选 {"1","5","15","30","60"}
        start: 开始日期,格式 YYYYMMDD
        end: 结束日期,格式 YYYYMMDD

    Returns:
        LOF分时行情 DataFrame
    """
    if end is None:
        end = datetime.now().strftime("%Y%m%d")
    if start is None:
        start = datetime.now().strftime("%Y%m%d")

    cache_key = f"lof_minute:{code}:{period}:{start}:{end}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.fund_lof_hist_min_em(
            symbol=code,
            period=period,
            start_date=start,
            end_date=end,
        )
        if df.empty:
            raise DataNotFoundError(f"LOF {code} 分时行情不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "时间": "time",
                "开盘": "open",
                "收盘": "close",
                "最高": "high",
                "最低": "low",
                "成交量": "volume",
                "成交额": "turnover",
                "均价": "avg_price",
            }
        )

        df["code"] = code
        result = df.reset_index(drop=True)

        # 缓存结果
        if self._cache:
            self._cache.set(cache_key, result, ttl=300)

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取LOF分时行情失败: {e}") from e

get_fund_bond_holdings

get_fund_bond_holdings(
    code: str, year: int | None = None
) -> pd.DataFrame

获取基金债券持仓

参数:

名称 类型 描述 默认
code str

基金代码

必需
year int | None

年份,如 2024,默认为当前年份

None

返回:

类型 描述
DataFrame

债券持仓 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_bond_holdings(
    self,
    code: str,
    year: int | None = None,  # type: ignore[override]
) -> pd.DataFrame:
    """
    获取基金债券持仓

    Args:
        code: 基金代码
        year: 年份,如 2024,默认为当前年份

    Returns:
        债券持仓 DataFrame
    """
    if year is None:
        year = datetime.now().year

    cache_key = f"fund_bond_holdings:{code}:{year}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.fund_portfolio_bond_hold_em(symbol=code, date=str(year))
        if df.empty:
            raise DataNotFoundError(f"基金 {code} 债券持仓数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "序号": "seq",
                "债券代码": "bond_code",
                "债券名称": "bond_name",
                "占净值比例": "weight",
                "持仓市值(万元)": "market_value",
                "季度": "quarter",
            }
        )

        df["fund_code"] = code
        result = df.reset_index(drop=True)

        # 缓存结果
        if self._cache:
            self._cache.set(cache_key, result, ttl=86400)

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金债券持仓失败: {e}") from e

get_fund_industry_allocation

get_fund_industry_allocation(
    code: str, year: int | None = None
) -> pd.DataFrame

获取基金行业配置

参数:

名称 类型 描述 默认
code str

基金代码

必需
year int | None

年份,如 2024,默认为当前年份

None

返回:

类型 描述
DataFrame

行业配置 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_industry_allocation(
    self,
    code: str,
    year: int | None = None,  # type: ignore[override]
) -> pd.DataFrame:
    """
    获取基金行业配置

    Args:
        code: 基金代码
        year: 年份,如 2024,默认为当前年份

    Returns:
        行业配置 DataFrame
    """
    if year is None:
        year = datetime.now().year

    cache_key = f"fund_industry_allocation:{code}:{year}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.fund_portfolio_industry_allocation_em(symbol=code, date=str(year))
        if df.empty:
            raise DataNotFoundError(f"基金 {code} 行业配置数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "序号": "seq",
                "行业类别": "industry",
                "占净值比例": "weight",
                "市值": "market_value",
                "截止时间": "report_date",
            }
        )

        df["fund_code"] = code
        result = df.reset_index(drop=True)

        # 缓存结果
        if self._cache:
            self._cache.set(cache_key, result, ttl=86400)

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金行业配置失败: {e}") from e

get_fund_portfolio_change

get_fund_portfolio_change(
    code: str,
    indicator: str = "累计买入",
    year: int | None = None,
) -> pd.DataFrame

获取基金重大变动(累计买入/卖出)

参数:

名称 类型 描述 默认
code str

基金代码

必需
indicator str

指标类型,可选 {"累计买入","累计卖出"}

'累计买入'
year int | None

年份,如 2024,默认为当前年份

None

返回:

类型 描述
DataFrame

重大变动 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_portfolio_change(
    self,
    code: str,
    indicator: str = "累计买入",
    year: int | None = None,  # type: ignore[override]
) -> pd.DataFrame:
    """
    获取基金重大变动(累计买入/卖出)

    Args:
        code: 基金代码
        indicator: 指标类型,可选 {"累计买入","累计卖出"}
        year: 年份,如 2024,默认为当前年份

    Returns:
        重大变动 DataFrame
    """
    if year is None:
        year = datetime.now().year

    cache_key = f"fund_portfolio_change:{code}:{indicator}:{year}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.fund_portfolio_change_em(
            symbol=code,
            indicator=indicator,
            date=str(year),
        )
        if df.empty:
            raise DataNotFoundError(f"基金 {code} 重大变动数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "序号": "seq",
                "股票代码": "stock_code",
                "股票名称": "stock_name",
                "本期累计买入/卖出金额": "amount",
                "占期初基金资产净值比例": "weight",
                "季度": "quarter",
            }
        )

        df["fund_code"] = code
        df["indicator"] = indicator
        result = df.reset_index(drop=True)

        # 缓存结果
        if self._cache:
            self._cache.set(cache_key, result, ttl=86400)

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金重大变动失败: {e}") from e

get_all_fund_managers

get_all_fund_managers() -> pd.DataFrame

获取基金经理大全

返回:

类型 描述
DataFrame

基金经理列表 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_all_fund_managers(self) -> pd.DataFrame:
    """
    获取基金经理大全

    Returns:
        基金经理列表 DataFrame
    """
    cache_key = "fund_all_managers"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.fund_manager_em()
        if df.empty:
            raise DataNotFoundError("无法获取基金经理数据")

        # 标准化列名
        df = df.rename(
            columns={
                "序号": "seq",
                "姓名": "name",
                "所属公司": "company",
                "现任基金代码": "current_fund_codes",
                "现任基金": "current_funds",
                "累计从业时间": "total_experience",
                "现任基金资产总规模": "total_scale",
                "现任基金最佳回报": "best_return",
            }
        )

        result = df.reset_index(drop=True)

        # 缓存结果
        if self._cache:
            self._cache.set(cache_key, result, ttl=86400)  # 缓存1天

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金经理大全失败: {e}") from e

get_macro_leverage_ratio

get_macro_leverage_ratio() -> pd.DataFrame

获取中国宏观杠杆率数据

返回:

类型 描述
DataFrame

宏观杠杆率 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_macro_leverage_ratio(self) -> pd.DataFrame:
    """
    获取中国宏观杠杆率数据

    Returns:
        宏观杠杆率 DataFrame
    """
    cache_key = "macro:leverage_ratio"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_cnbs()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取宏观杠杆率数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取宏观杠杆率失败: {e}") from e

get_enterprise_price_index

get_enterprise_price_index() -> pd.DataFrame

获取企业商品价格指数

返回:

类型 描述
DataFrame

企业商品价格指数 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_enterprise_price_index(self) -> pd.DataFrame:
    """
    获取企业商品价格指数

    Returns:
        企业商品价格指数 DataFrame
    """
    cache_key = "macro:enterprise_price_index"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_qyspjg()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取企业商品价格指数数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取企业商品价格指数失败: {e}") from e

get_fdi_data

get_fdi_data() -> pd.DataFrame

获取外商直接投资数据

返回:

类型 描述
DataFrame

FDI数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fdi_data(self) -> pd.DataFrame:
    """
    获取外商直接投资数据

    Returns:
        FDI数据 DataFrame
    """
    cache_key = "macro:fdi_data"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_fdi()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取外商直接投资数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取FDI数据失败: {e}") from e

get_lpr_data

get_lpr_data() -> pd.DataFrame

获取LPR品种数据

返回:

类型 描述
DataFrame

LPR数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_lpr_data(self) -> pd.DataFrame:
    """
    获取LPR品种数据

    Returns:
        LPR数据 DataFrame
    """
    cache_key = "macro:lpr_data"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_lpr()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取LPR数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取LPR数据失败: {e}") from e

get_urban_unemployment

get_urban_unemployment() -> pd.DataFrame

获取城镇调查失业率

返回:

类型 描述
DataFrame

失业率数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_urban_unemployment(self) -> pd.DataFrame:
    """
    获取城镇调查失业率

    Returns:
        失业率数据 DataFrame
    """
    cache_key = "macro:urban_unemployment"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_urban_unemployment()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取城镇失业率数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取城镇失业率失败: {e}") from e

get_social_financing

get_social_financing() -> pd.DataFrame

获取社会融资规模增量统计

返回:

类型 描述
DataFrame

社融数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_social_financing(self) -> pd.DataFrame:
    """
    获取社会融资规模增量统计

    Returns:
        社融数据 DataFrame
    """
    cache_key = "macro:social_financing"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_shrzgm()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取社会融资规模数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取社会融资规模失败: {e}") from e

get_gdp_yearly

get_gdp_yearly() -> pd.DataFrame

获取中国GDP年率数据

返回:

类型 描述
DataFrame

GDP年率 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_gdp_yearly(self) -> pd.DataFrame:
    """
    获取中国GDP年率数据

    Returns:
        GDP年率 DataFrame
    """
    cache_key = "macro:gdp_yearly"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_gdp_yearly()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取GDP年率数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取GDP年率失败: {e}") from e

get_gdp_quarterly

get_gdp_quarterly() -> pd.DataFrame

获取中国GDP季度数据

返回:

类型 描述
DataFrame

GDP季度数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_gdp_quarterly(self) -> pd.DataFrame:
    """
    获取中国GDP季度数据

    Returns:
        GDP季度数据 DataFrame
    """
    cache_key = "macro:gdp_quarterly"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_gdp_quarterly()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取GDP季度数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取GDP季度数据失败: {e}") from e

get_cpi_yearly

get_cpi_yearly() -> pd.DataFrame

获取中国CPI年率数据

返回:

类型 描述
DataFrame

CPI年率 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_cpi_yearly(self) -> pd.DataFrame:
    """
    获取中国CPI年率数据

    Returns:
        CPI年率 DataFrame
    """
    cache_key = "macro:cpi_yearly"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_cpi_yearly()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取CPI年率数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取CPI年率失败: {e}") from e

get_cpi_monthly

get_cpi_monthly() -> pd.DataFrame

获取中国CPI月率数据

返回:

类型 描述
DataFrame

CPI月率 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_cpi_monthly(self) -> pd.DataFrame:
    """
    获取中国CPI月率数据

    Returns:
        CPI月率 DataFrame
    """
    cache_key = "macro:cpi_monthly"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_cpi_monthly()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取CPI月率数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取CPI月率失败: {e}") from e

get_ppi_yearly

get_ppi_yearly() -> pd.DataFrame

获取中国PPI年率数据

返回:

类型 描述
DataFrame

PPI年率 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_ppi_yearly(self) -> pd.DataFrame:
    """
    获取中国PPI年率数据

    Returns:
        PPI年率 DataFrame
    """
    cache_key = "macro:ppi_yearly"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_ppi_yearly()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取PPI年率数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取PPI年率失败: {e}") from e

get_ppi_monthly

get_ppi_monthly() -> pd.DataFrame

获取中国PPI月率数据

返回:

类型 描述
DataFrame

PPI月率 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_ppi_monthly(self) -> pd.DataFrame:
    """
    获取中国PPI月率数据

    Returns:
        PPI月率 DataFrame
    """
    cache_key = "macro:ppi_monthly"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_ppi_monthly()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取PPI月率数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取PPI月率失败: {e}") from e

get_exports_yearly

get_exports_yearly() -> pd.DataFrame

获取出口年率数据

返回:

类型 描述
DataFrame

出口年率 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_exports_yearly(self) -> pd.DataFrame:
    """
    获取出口年率数据

    Returns:
        出口年率 DataFrame
    """
    cache_key = "macro:exports_yearly"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_exports_yearly()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取出口年率数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取出口年率失败: {e}") from e

get_imports_yearly

get_imports_yearly() -> pd.DataFrame

获取进口年率数据

返回:

类型 描述
DataFrame

进口年率 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_imports_yearly(self) -> pd.DataFrame:
    """
    获取进口年率数据

    Returns:
        进口年率 DataFrame
    """
    cache_key = "macro:imports_yearly"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_imports_yearly()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取进口年率数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取进口年率失败: {e}") from e

get_trade_balance

get_trade_balance() -> pd.DataFrame

获取贸易帐数据

返回:

类型 描述
DataFrame

贸易帐 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_trade_balance(self) -> pd.DataFrame:
    """
    获取贸易帐数据

    Returns:
        贸易帐 DataFrame
    """
    cache_key = "macro:trade_balance"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_trade_balance()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取贸易帐数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取贸易帐失败: {e}") from e

get_industrial_production

get_industrial_production() -> pd.DataFrame

获取工业增加值增长数据

返回:

类型 描述
DataFrame

工业增加值 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_industrial_production(self) -> pd.DataFrame:
    """
    获取工业增加值增长数据

    Returns:
        工业增加值 DataFrame
    """
    cache_key = "macro:industrial_production"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_industrial_production_yearly()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取工业增加值数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取工业增加值失败: {e}") from e

get_pmi_official

get_pmi_official() -> pd.DataFrame

获取官方制造业PMI数据

返回:

类型 描述
DataFrame

官方PMI DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_pmi_official(self) -> pd.DataFrame:
    """
    获取官方制造业PMI数据

    Returns:
        官方PMI DataFrame
    """
    cache_key = "macro:pmi_official"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_pmi_yearly()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取官方PMI数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取官方PMI失败: {e}") from e

get_pmi_caixin

get_pmi_caixin() -> pd.DataFrame

获取财新制造业PMI数据

返回:

类型 描述
DataFrame

财新PMI DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_pmi_caixin(self) -> pd.DataFrame:
    """
    获取财新制造业PMI数据

    Returns:
        财新PMI DataFrame
    """
    cache_key = "macro:pmi_caixin"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_cx_pmi_yearly()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取财新PMI数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取财新PMI失败: {e}") from e

get_services_pmi

get_services_pmi() -> pd.DataFrame

获取财新服务业PMI数据

返回:

类型 描述
DataFrame

服务业PMI DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_services_pmi(self) -> pd.DataFrame:
    """
    获取财新服务业PMI数据

    Returns:
        服务业PMI DataFrame
    """
    cache_key = "macro:services_pmi"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_cx_services_pmi()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取服务业PMI数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取服务业PMI失败: {e}") from e

get_non_manufacturing_pmi

get_non_manufacturing_pmi() -> pd.DataFrame

获取官方非制造业PMI数据

返回:

类型 描述
DataFrame

非制造业PMI DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_non_manufacturing_pmi(self) -> pd.DataFrame:
    """
    获取官方非制造业PMI数据

    Returns:
        非制造业PMI DataFrame
    """
    cache_key = "macro:non_manufacturing_pmi"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_non_man_pmi()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取非制造业PMI数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取非制造业PMI失败: {e}") from e

get_m2_yearly

get_m2_yearly() -> pd.DataFrame

获取M2货币供应年率数据

返回:

类型 描述
DataFrame

M2数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_m2_yearly(self) -> pd.DataFrame:
    """
    获取M2货币供应年率数据

    Returns:
        M2数据 DataFrame
    """
    cache_key = "macro:m2_yearly"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_m2_yearly()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取M2货币供应数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取M2数据失败: {e}") from e

get_new_loan

get_new_loan() -> pd.DataFrame

获取新增人民币贷款数据

返回:

类型 描述
DataFrame

新增贷款 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_new_loan(self) -> pd.DataFrame:
    """
    获取新增人民币贷款数据

    Returns:
        新增贷款 DataFrame
    """
    cache_key = "macro:new_loan"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_new_loan()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取新增贷款数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取新增贷款失败: {e}") from e

get_retail_sales_yearly

get_retail_sales_yearly() -> pd.DataFrame

获取社会消费品零售总额年率数据

返回:

类型 描述
DataFrame

零售销售 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_retail_sales_yearly(self) -> pd.DataFrame:
    """
    获取社会消费品零售总额年率数据

    Returns:
        零售销售 DataFrame
    """
    cache_key = "macro:retail_sales_yearly"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_retail_sales_yearly()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取零售销售数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取零售销售失败: {e}") from e

get_fixed_asset_investment

get_fixed_asset_investment() -> pd.DataFrame

获取固定资产投资年率数据

返回:

类型 描述
DataFrame

固定资产投资 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fixed_asset_investment(self) -> pd.DataFrame:
    """
    获取固定资产投资年率数据

    Returns:
        固定资产投资 DataFrame
    """
    cache_key = "macro:fixed_asset_investment"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_fixed_asset_investment_yearly()

        if df is None or df.empty:
            raise DataNotFoundError("无法获取固定资产投资数据")

        # 列名标准化
        df = self._standardize_columns(df)

        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)

        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取固定资产投资失败: {e}") from e

get_china_interest_rate

get_china_interest_rate() -> pd.DataFrame

获取中国央行利率决议数据

返回:

类型 描述
DataFrame

中国利率 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_china_interest_rate(self) -> pd.DataFrame:
    """
    获取中国央行利率决议数据

    Returns:
        中国利率 DataFrame
    """
    cache_key = "interest_rate:china"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_bank_china_interest_rate()
        if df.empty:
            raise DataNotFoundError("中国央行利率数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "日期": "date",
                "利率": "rate",
                "利率类型": "rate_type",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=3600)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取中国利率失败: {e}") from e

get_usa_interest_rate

get_usa_interest_rate() -> pd.DataFrame

获取美联储利率决议数据

返回:

类型 描述
DataFrame

美国利率 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_usa_interest_rate(self) -> pd.DataFrame:
    """
    获取美联储利率决议数据

    Returns:
        美国利率 DataFrame
    """
    cache_key = "interest_rate:usa"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_bank_usa_interest_rate()
        if df.empty:
            raise DataNotFoundError("美联储利率数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "日期": "date",
                "利率": "rate",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=3600)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取美国利率失败: {e}") from e

get_euro_interest_rate

get_euro_interest_rate() -> pd.DataFrame

获取欧洲央行利率决议数据

返回:

类型 描述
DataFrame

欧元区利率 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_euro_interest_rate(self) -> pd.DataFrame:
    """
    获取欧洲央行利率决议数据

    Returns:
        欧元区利率 DataFrame
    """
    cache_key = "interest_rate:euro"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_bank_euro_interest_rate()
        if df.empty:
            raise DataNotFoundError("欧洲央行利率数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "日期": "date",
                "利率": "rate",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=3600)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取欧元区利率失败: {e}") from e

get_japan_interest_rate

get_japan_interest_rate() -> pd.DataFrame

获取日本央行利率决议数据

返回:

类型 描述
DataFrame

日本利率 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_japan_interest_rate(self) -> pd.DataFrame:
    """
    获取日本央行利率决议数据

    Returns:
        日本利率 DataFrame
    """
    cache_key = "interest_rate:japan"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_bank_japan_interest_rate()
        if df.empty:
            raise DataNotFoundError("日本央行利率数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "日期": "date",
                "利率": "rate",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=3600)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取日本利率失败: {e}") from e

get_uk_interest_rate

get_uk_interest_rate() -> pd.DataFrame

获取英国央行利率决议数据

返回:

类型 描述
DataFrame

英国利率 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_uk_interest_rate(self) -> pd.DataFrame:
    """
    获取英国央行利率决议数据

    Returns:
        英国利率 DataFrame
    """
    cache_key = "interest_rate:uk"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_bank_uk_interest_rate()
        if df.empty:
            raise DataNotFoundError("英国央行利率数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "日期": "date",
                "利率": "rate",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=3600)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取英国利率失败: {e}") from e

get_shibor

get_shibor() -> pd.DataFrame

获取SHIBOR利率数据

返回:

类型 描述
DataFrame

SHIBOR DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_shibor(self) -> pd.DataFrame:
    """
    获取SHIBOR利率数据

    Returns:
        SHIBOR DataFrame
    """
    cache_key = "interest_rate:shibor"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_shibor()
        if df.empty:
            raise DataNotFoundError("SHIBOR利率数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "日期": "date",
                "O/N": "overnight",
                "1W": "week_1",
                "2W": "week_2",
                "1M": "month_1",
                "3M": "month_3",
                "6M": "month_6",
                "9M": "month_9",
                "1Y": "year_1",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=3600)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取SHIBOR失败: {e}") from e

get_shibor_lpr

get_shibor_lpr() -> pd.DataFrame

获取SHIBOR-LPR数据

返回:

类型 描述
DataFrame

SHIBOR-LPR DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_shibor_lpr(self) -> pd.DataFrame:
    """
    获取SHIBOR-LPR数据

    Returns:
        SHIBOR-LPR DataFrame
    """
    cache_key = "interest_rate:shibor_lpr"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_shibor_lpr()
        if df.empty:
            raise DataNotFoundError("SHIBOR-LPR数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "日期": "date",
                "LPR1Y": "lpr_1y",
                "LPR5Y": "lpr_5y",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=3600)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取SHIBOR-LPR失败: {e}") from e

get_hibor

get_hibor() -> pd.DataFrame

获取HIBOR利率数据

返回:

类型 描述
DataFrame

HIBOR DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_hibor(self) -> pd.DataFrame:
    """
    获取HIBOR利率数据

    Returns:
        HIBOR DataFrame
    """
    cache_key = "interest_rate:hibor"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.macro_china_hibor()
        if df.empty:
            raise DataNotFoundError("HIBOR利率数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "日期": "date",
                "O/N": "overnight",
                "1W": "week_1",
                "2W": "week_2",
                "1M": "month_1",
                "3M": "month_3",
                "6M": "month_6",
                "9M": "month_9",
                "1Y": "year_1",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=3600)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取HIBOR失败: {e}") from e

get_industry_boards

get_industry_boards() -> pd.DataFrame

获取行业板块名称列表

返回:

类型 描述
DataFrame

行业板块列表 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_industry_boards(self) -> pd.DataFrame:
    """
    获取行业板块名称列表

    Returns:
        行业板块列表 DataFrame
    """
    cache_key = "industry_boards:list"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.stock_board_industry_name_em()
        if df.empty:
            raise DataNotFoundError("行业板块数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "板块名称": "name",
                "板块代码": "code",
                "最新价": "price",
                "涨跌幅": "change_pct",
                "涨跌额": "change",
                "成交量": "volume",
                "成交额": "amount",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=300)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取行业板块列表失败: {e}") from e

get_industry_board_hist

get_industry_board_hist(
    code: str,
    period: str = "daily",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame

获取行业板块历史行情

参数:

名称 类型 描述 默认
code str

板块代码

必需
period str

周期 (daily/weekly/monthly)

'daily'
start_date str | None

开始日期 (YYYYMMDD)

None
end_date str | None

结束日期 (YYYYMMDD)

None

返回:

类型 描述
DataFrame

行业板块历史数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_industry_board_hist(
    self,
    code: str,
    period: str = "daily",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame:
    """
    获取行业板块历史行情

    Args:
        code: 板块代码
        period: 周期 (daily/weekly/monthly)
        start_date: 开始日期 (YYYYMMDD)
        end_date: 结束日期 (YYYYMMDD)

    Returns:
        行业板块历史数据 DataFrame
    """
    cache_key = f"industry_board_hist:{code}:{period}:{start_date}:{end_date}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.stock_board_industry_hist_em(
            symbol=code, period=period, start_date=start_date, end_date=end_date
        )
        if df.empty:
            raise DataNotFoundError(f"行业板块 {code} 历史数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "日期": "date",
                "开盘": "open",
                "收盘": "close",
                "最高": "high",
                "最低": "low",
                "成交量": "volume",
                "成交额": "amount",
                "振幅": "amplitude",
                "涨跌幅": "change_pct",
                "涨跌额": "change",
                "换手率": "turnover",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=300)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取行业板块历史数据失败: {e}") from e

get_concept_boards

get_concept_boards() -> pd.DataFrame

获取概念板块名称列表

返回:

类型 描述
DataFrame

概念板块列表 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_concept_boards(self) -> pd.DataFrame:
    """
    获取概念板块名称列表

    Returns:
        概念板块列表 DataFrame
    """
    cache_key = "concept_boards:list"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.stock_board_concept_name_em()
        if df.empty:
            raise DataNotFoundError("概念板块数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "板块名称": "name",
                "板块代码": "code",
                "最新价": "price",
                "涨跌幅": "change_pct",
                "涨跌额": "change",
                "成交量": "volume",
                "成交额": "amount",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=300)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取概念板块列表失败: {e}") from e

get_concept_board_hist

get_concept_board_hist(
    code: str,
    period: str = "daily",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame

获取概念板块历史行情

参数:

名称 类型 描述 默认
code str

板块代码

必需
period str

周期 (daily/weekly/monthly)

'daily'
start_date str | None

开始日期 (YYYYMMDD)

None
end_date str | None

结束日期 (YYYYMMDD)

None

返回:

类型 描述
DataFrame

概念板块历史数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_concept_board_hist(
    self,
    code: str,
    period: str = "daily",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame:
    """
    获取概念板块历史行情

    Args:
        code: 板块代码
        period: 周期 (daily/weekly/monthly)
        start_date: 开始日期 (YYYYMMDD)
        end_date: 结束日期 (YYYYMMDD)

    Returns:
        概念板块历史数据 DataFrame
    """
    cache_key = f"concept_board_hist:{code}:{period}:{start_date}:{end_date}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.stock_board_concept_hist_em(
            symbol=code, period=period, start_date=start_date, end_date=end_date
        )
        if df.empty:
            raise DataNotFoundError(f"概念板块 {code} 历史数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "日期": "date",
                "开盘": "open",
                "收盘": "close",
                "最高": "high",
                "最低": "low",
                "成交量": "volume",
                "成交额": "amount",
                "振幅": "amplitude",
                "涨跌幅": "change_pct",
                "涨跌额": "change",
                "换手率": "turnover",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=300)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取概念板块历史数据失败: {e}") from e

get_sector_fund_flow

get_sector_fund_flow(period: str = '今日') -> pd.DataFrame

获取板块资金流向

参数:

名称 类型 描述 默认
period str

统计周期 ("今日","5日","10日")

'今日'

返回:

类型 描述
DataFrame

板块资金流向 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_sector_fund_flow(self, period: str = "今日") -> pd.DataFrame:
    """
    获取板块资金流向

    Args:
        period: 统计周期 ("今日","5日","10日")

    Returns:
        板块资金流向 DataFrame
    """
    cache_key = f"sector_fund_flow:{period}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.stock_sector_spot(indicator=period)
        if df.empty:
            raise DataNotFoundError(f"板块资金流向数据不存在 (周期: {period})")

        # 标准化列名
        df = df.rename(
            columns={
                "名称": "name",
                "今日涨跌幅": "change_pct",
                "今日主力净流入": "main_inflow",
                "今日小单净流入": "small_inflow",
                "今日中单净流入": "medium_inflow",
                "今日大单净流入": "large_inflow",
                "今日超大单净流入": "xlarge_inflow",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=300)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取板块资金流向失败: {e}") from e

get_china_us_bond_yield

get_china_us_bond_yield() -> pd.DataFrame

获取中美国债收益率数据

返回:

类型 描述
DataFrame

中美国债收益率 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_china_us_bond_yield(self) -> pd.DataFrame:
    """
    获取中美国债收益率数据

    Returns:
        中美国债收益率 DataFrame
    """
    cache_key = "bond_yield:china_us"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.bond_zh_us_rate()
        if df.empty:
            raise DataNotFoundError("中美国债收益率数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "日期": "date",
                "中国国债收益率2Y": "china_2y",
                "中国国债收益率5Y": "china_5y",
                "中国国债收益率10Y": "china_10y",
                "中国国债收益率30Y": "china_30y",
                "美国国债收益率2Y": "usa_2y",
                "美国国债收益率5Y": "usa_5y",
                "美国国债收益率10Y": "usa_10y",
                "美国国债收益率30Y": "usa_30y",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=300)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取中美国债收益率失败: {e}") from e

get_bond_yield_curve

get_bond_yield_curve(
    bond_type: str = "国债",
    period: str = "daily",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame

获取收盘收益率曲线历史数据

参数:

名称 类型 描述 默认
bond_type str

债券类型 ("国债","国开债","农发债","进出口行债")

'国债'
period str

周期

'daily'
start_date str | None

开始日期 (YYYYMMDD)

None
end_date str | None

结束日期 (YYYYMMDD)

None

返回:

类型 描述
DataFrame

收益率曲线 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_bond_yield_curve(
    self,
    bond_type: str = "国债",
    period: str = "daily",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame:
    """
    获取收盘收益率曲线历史数据

    Args:
        bond_type: 债券类型 ("国债","国开债","农发债","进出口行债")
        period: 周期
        start_date: 开始日期 (YYYYMMDD)
        end_date: 结束日期 (YYYYMMDD)

    Returns:
        收益率曲线 DataFrame
    """
    cache_key = f"bond_yield_curve:{bond_type}:{period}:{start_date}:{end_date}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.bond_china_close_return(
            symbol=bond_type, period=period, start_date=start_date, end_date=end_date
        )
        if df.empty:
            raise DataNotFoundError(f"债券收益率曲线数据不存在 (类型: {bond_type})")

        # 标准化列名
        df = df.rename(
            columns={
                "date": "date",
                "1y": "yield_1y",
                "2y": "yield_2y",
                "3y": "yield_3y",
                "5y": "yield_5y",
                "7y": "yield_7y",
                "10y": "yield_10y",
                "30y": "yield_30y",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=300)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取债券收益率曲线失败: {e}") from e

get_bond_spot_quote

get_bond_spot_quote() -> pd.DataFrame

获取现券市场做市报价

返回:

类型 描述
DataFrame

现券报价 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_bond_spot_quote(self) -> pd.DataFrame:
    """
    获取现券市场做市报价

    Returns:
        现券报价 DataFrame
    """
    cache_key = "bond_spot_quote:list"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.bond_spot_quote()
        if df.empty:
            raise DataNotFoundError("现券报价数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "债券简称": "name",
                "债券代码": "code",
                "买入价": "bid_price",
                "卖出价": "ask_price",
                "到期收益率": "yield",
                "剩余期限": "remaining_term",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=300)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取现券报价失败: {e}") from e

get_convertible_bonds

get_convertible_bonds() -> pd.DataFrame

获取可转债数据一览表

返回:

类型 描述
DataFrame

可转债列表 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_convertible_bonds(self) -> pd.DataFrame:
    """
    获取可转债数据一览表

    Returns:
        可转债列表 DataFrame
    """
    cache_key = "convertible_bonds:list"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.bond_cb_info_jsl()
        if df.empty:
            raise DataNotFoundError("可转债数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "债券代码": "code",
                "债券简称": "name",
                "现价": "price",
                "涨跌幅": "change_pct",
                "正股代码": "stock_code",
                "正股名称": "stock_name",
                "正股价": "stock_price",
                "转股价": "convert_price",
                "转股价值": "convert_value",
                "溢价率": "premium_rate",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=300)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取可转债列表失败: {e}") from e

get_convertible_bond_detail

get_convertible_bond_detail(code: str) -> pd.DataFrame

获取可转债详情数据

参数:

名称 类型 描述 默认
code str

可转债代码

必需

返回:

类型 描述
DataFrame

可转债详情 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_convertible_bond_detail(self, code: str) -> pd.DataFrame:
    """
    获取可转债详情数据

    Args:
        code: 可转债代码

    Returns:
        可转债详情 DataFrame
    """
    cache_key = f"convertible_bond_detail:{code}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.bond_cb_detail_jsl(symbol=code)
        if df.empty:
            raise DataNotFoundError(f"可转债 {code} 详情数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "项目": "item",
                "值": "value",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=300)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取可转债详情失败: {e}") from e

get_bond_spot

get_bond_spot(code: str) -> pd.DataFrame

获取沪深债券实时行情

参数:

名称 类型 描述 默认
code str

债券代码

必需

返回:

类型 描述
DataFrame

债券实时行情 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_bond_spot(self, code: str) -> pd.DataFrame:
    """
    获取沪深债券实时行情

    Args:
        code: 债券代码

    Returns:
        债券实时行情 DataFrame
    """
    cache_key = f"bond_spot:{code}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.bond_zh_hs_cov_spot(symbol=code)
        if df.empty:
            raise DataNotFoundError(f"债券 {code} 实时行情不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "债券代码": "code",
                "债券简称": "name",
                "最新价": "price",
                "涨跌幅": "change_pct",
                "涨跌额": "change",
                "成交量": "volume",
                "成交额": "amount",
                "最高": "high",
                "最低": "low",
                "今开": "open",
                "昨收": "prev_close",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=300)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取债券实时行情失败: {e}") from e

get_bond_hist

get_bond_hist(
    code: str,
    period: str = "daily",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame

获取沪深债券历史行情

参数:

名称 类型 描述 默认
code str

债券代码

必需
period str

周期 (daily/weekly/monthly)

'daily'
start_date str | None

开始日期 (YYYYMMDD)

None
end_date str | None

结束日期 (YYYYMMDD)

None

返回:

类型 描述
DataFrame

债券历史数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_bond_hist(
    self,
    code: str,
    period: str = "daily",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame:
    """
    获取沪深债券历史行情

    Args:
        code: 债券代码
        period: 周期 (daily/weekly/monthly)
        start_date: 开始日期 (YYYYMMDD)
        end_date: 结束日期 (YYYYMMDD)

    Returns:
        债券历史数据 DataFrame
    """
    cache_key = f"bond_hist:{code}:{period}:{start_date}:{end_date}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.bond_zh_hs_cov_hist(
            symbol=code, period=period, start_date=start_date, end_date=end_date
        )
        if df.empty:
            raise DataNotFoundError(f"债券 {code} 历史数据不存在")

        # 标准化列名
        df = df.rename(
            columns={
                "日期": "date",
                "开盘": "open",
                "收盘": "close",
                "最高": "high",
                "最低": "low",
                "成交量": "volume",
                "成交额": "amount",
                "振幅": "amplitude",
                "涨跌幅": "change_pct",
                "涨跌额": "change",
            }
        )

        result = df.reset_index(drop=True)
        if self._cache:
            self._cache.set(cache_key, result, ttl=300)
        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取债券历史数据失败: {e}") from e

get_a_share_valuation

get_a_share_valuation() -> pd.DataFrame

获取A股等权重与中位数市盈率/市净率

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_a_share_valuation(self) -> pd.DataFrame:
    """获取A股等权重与中位数市盈率/市净率"""
    cache_key = "a_share_valuation"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.stock_a_pe_and_pb()
        if df is None or df.empty:
            raise DataNotFoundError("无法获取A股估值数据")
        df = self._standardize_columns(df)
        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)
        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取A股估值失败: {e}") from e

get_stock_valuation_lg

get_stock_valuation_lg(code: str) -> pd.DataFrame

获取个股估值数据(乐咕乐股)

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_stock_valuation_lg(self, code: str) -> pd.DataFrame:
    """获取个股估值数据(乐咕乐股)"""
    cache_key = f"stock_valuation_lg:{code}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.stock_a_indicator_lg(symbol=code)
        if df is None or df.empty:
            raise DataNotFoundError(f"无法获取个股估值数据: {code}")
        df = self._standardize_columns(df)
        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)
        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取个股估值失败: {e}") from e

get_index_valuation

get_index_valuation(
    code: str, indicator: str = "pe"
) -> pd.DataFrame

获取指数估值历史数据(乐咕乐股)

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_index_valuation(
    self, code: str, indicator: str = "pe"
) -> pd.DataFrame:
    """获取指数估值历史数据(乐咕乐股)"""
    cache_key = f"index_valuation:{code}:{indicator}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.index_value_hist_funddb(symbol=code, indicator=indicator)
        if df is None or df.empty:
            raise DataNotFoundError(f"无法获取指数估值数据: {code}")
        df = self._standardize_columns(df)
        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)
        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取指数估值失败: {e}") from e

get_market_pe_lg

get_market_pe_lg(code: str) -> pd.DataFrame

获取指数市盈率数据(乐咕乐股)

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_market_pe_lg(self, code: str) -> pd.DataFrame:
    """获取指数市盈率数据(乐咕乐股)"""
    cache_key = f"market_pe_lg:{code}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.stock_market_pe_lg(symbol=code)
        if df is None or df.empty:
            raise DataNotFoundError(f"无法获取市场PE数据: {code}")
        df = self._standardize_columns(df)
        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)
        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取市场PE失败: {e}") from e

get_market_pb_lg

get_market_pb_lg(code: str) -> pd.DataFrame

获取指数市净率数据(乐咕乐股)

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_market_pb_lg(self, code: str) -> pd.DataFrame:
    """获取指数市净率数据(乐咕乐股)"""
    cache_key = f"market_pb_lg:{code}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.stock_market_pb_lg(symbol=code)
        if df is None or df.empty:
            raise DataNotFoundError(f"无法获取市场PB数据: {code}")
        df = self._standardize_columns(df)
        if self._cache:
            self._cache.set(cache_key, df, ttl=86400)
        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取市场PB失败: {e}") from e

get_market_fund_flow

get_market_fund_flow() -> pd.DataFrame

获取大盘资金流向数据

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_market_fund_flow(self) -> pd.DataFrame:
    """获取大盘资金流向数据"""
    cache_key = "market_fund_flow"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.stock_market_fund_flow()
        if df is None or df.empty:
            raise DataNotFoundError("无法获取大盘资金流向数据")
        df = self._standardize_columns(df)
        if self._cache:
            self._cache.set(cache_key, df, ttl=60)
        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取大盘资金流向失败: {e}") from e

get_stock_fund_flow

get_stock_fund_flow(
    code: str, market: str = "sh"
) -> pd.DataFrame

获取个股资金流向数据

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_stock_fund_flow(self, code: str, market: str = "sh") -> pd.DataFrame:
    """获取个股资金流向数据"""
    cache_key = f"stock_fund_flow:{code}:{market}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.stock_individual_fund_flow(stock=code, market=market)
        if df is None or df.empty:
            raise DataNotFoundError(f"无法获取个股资金流向数据: {code}")
        df = self._standardize_columns(df)
        if self._cache:
            self._cache.set(cache_key, df, ttl=60)
        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取个股资金流向失败: {e}") from e

get_north_fund_flow

get_north_fund_flow(market: str = '北向资金') -> pd.DataFrame

获取北向资金流向数据

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_north_fund_flow(self, market: str = "北向资金") -> pd.DataFrame:
    """获取北向资金流向数据"""
    cache_key = f"north_fund_flow:{market}"
    if self._cache and self._cache.exists(cache_key):
        return self._cache.get(cache_key)

    ak = self._get_akshare()
    try:
        df = ak.stock_hsgt_north_net_flow_in_em(symbol=market)
        if df is None or df.empty:
            raise DataNotFoundError(f"无法获取北向资金流向数据: {market}")
        df = self._standardize_columns(df)
        if self._cache:
            self._cache.set(cache_key, df, ttl=60)
        return df
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取北向资金流向失败: {e}") from e

get_fund_company_aum

get_fund_company_aum() -> pd.DataFrame

基金公司管理规模排名

返回:

类型 描述
DataFrame

基金公司规模排名 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_company_aum(self) -> pd.DataFrame:
    """
    基金公司管理规模排名

    Returns:
        基金公司规模排名 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_aum_em()
        if df.empty:
            raise DataNotFoundError("基金公司规模数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金公司规模排名失败: {e}") from e

get_fund_aum_trend

get_fund_aum_trend() -> pd.DataFrame

基金市场管理规模走势

返回:

类型 描述
DataFrame

规模走势 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_aum_trend(self) -> pd.DataFrame:
    """
    基金市场管理规模走势

    Returns:
        规模走势 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_aum_trend_em()
        if df.empty:
            raise DataNotFoundError("基金规模走势数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金规模走势失败: {e}") from e

get_fund_company_aum_history

get_fund_company_aum_history(
    year: int | None = None,
) -> pd.DataFrame

基金公司历年管理规模

参数:

名称 类型 描述 默认
year int | None

年份,如 2023

None

返回:

类型 描述
DataFrame

历年规模数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_company_aum_history(self, year: int | None = None) -> pd.DataFrame:
    """
    基金公司历年管理规模

    Args:
        year: 年份,如 2023

    Returns:
        历年规模数据 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_aum_hist_em(year=year)
        if df.empty:
            raise DataNotFoundError(f"{year}年基金公司规模数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金公司历年规模失败: {e}") from e

get_fund_scale_change

get_fund_scale_change() -> pd.DataFrame

规模变动(全市场汇总)

返回:

类型 描述
DataFrame

规模变动数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_scale_change(self) -> pd.DataFrame:
    """
    规模变动(全市场汇总)

    Returns:
        规模变动数据 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_scale_change_em()
        if df.empty:
            raise DataNotFoundError("基金规模变动数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金规模变动失败: {e}") from e

get_fund_holder_structure

get_fund_holder_structure() -> pd.DataFrame

持有人结构

返回:

类型 描述
DataFrame

持有人结构数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_holder_structure(self) -> pd.DataFrame:
    """
    持有人结构

    Returns:
        持有人结构数据 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_hold_structure_em()
        if df.empty:
            raise DataNotFoundError("基金持有人结构数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金持有人结构失败: {e}") from e

get_fund_ratings

get_fund_ratings() -> pd.DataFrame

基金评级总汇

返回:

类型 描述
DataFrame

基金评级汇总 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_ratings(self) -> pd.DataFrame:
    """
    基金评级总汇

    Returns:
        基金评级汇总 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_rating_all()
        if df.empty:
            raise DataNotFoundError("基金评级数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金评级总汇失败: {e}") from e

get_fund_rating_sh

get_fund_rating_sh(date: str | None = None) -> pd.DataFrame

上海证券评级

参数:

名称 类型 描述 默认
date str | None

日期,格式 YYYYMMDD

None

返回:

类型 描述
DataFrame

上海证券评级数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_rating_sh(self, date: str | None = None) -> pd.DataFrame:
    """
    上海证券评级

    Args:
        date: 日期,格式 YYYYMMDD

    Returns:
        上海证券评级数据 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_rating_sh(date=date)
        if df.empty:
            raise DataNotFoundError(f"上海证券评级数据不存在 (日期: {date})")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取上海证券评级失败: {e}") from e

get_fund_rating_zs

get_fund_rating_zs(date: str | None = None) -> pd.DataFrame

招商证券评级

参数:

名称 类型 描述 默认
date str | None

日期,格式 YYYYMMDD

None

返回:

类型 描述
DataFrame

招商证券评级数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_rating_zs(self, date: str | None = None) -> pd.DataFrame:
    """
    招商证券评级

    Args:
        date: 日期,格式 YYYYMMDD

    Returns:
        招商证券评级数据 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_rating_zs(date=date)
        if df.empty:
            raise DataNotFoundError(f"招商证券评级数据不存在 (日期: {date})")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取招商证券评级失败: {e}") from e

get_fund_rating_ja

get_fund_rating_ja(date: str | None = None) -> pd.DataFrame

济安金信评级

参数:

名称 类型 描述 默认
date str | None

日期,格式 YYYYMMDD

None

返回:

类型 描述
DataFrame

济安金信评级数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_rating_ja(self, date: str | None = None) -> pd.DataFrame:
    """
    济安金信评级

    Args:
        date: 日期,格式 YYYYMMDD

    Returns:
        济安金信评级数据 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_rating_ja(date=date)
        if df.empty:
            raise DataNotFoundError(f"济安金信评级数据不存在 (日期: {date})")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取济安金信评级失败: {e}") from e

get_fund_dividends

get_fund_dividends(
    year: int | None = None,
    fund_type: str | None = None,
    page: int = -1,
) -> pd.DataFrame

基金分红

参数:

名称 类型 描述 默认
year int | None

年份

None
fund_type str | None

基金类型

None
page int

页码,-1表示全部

-1

返回:

类型 描述
DataFrame

基金分红数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_dividends(
    self,
    year: int | None = None,
    fund_type: str | None = None,
    page: int = -1,
) -> pd.DataFrame:
    """
    基金分红

    Args:
        year: 年份
        fund_type: 基金类型
        page: 页码,-1表示全部

    Returns:
        基金分红数据 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_fh_em(year=year, typ=fund_type, page=page)
        if df.empty:
            raise DataNotFoundError("基金分红数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金分红数据失败: {e}") from e

get_fund_splits

get_fund_splits(
    year: int | None = None,
    fund_type: str | None = None,
    page: int = -1,
) -> pd.DataFrame

基金拆分

参数:

名称 类型 描述 默认
year int | None

年份

None
fund_type str | None

基金类型

None
page int

页码,-1表示全部

-1

返回:

类型 描述
DataFrame

基金拆分数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_splits(
    self,
    year: int | None = None,
    fund_type: str | None = None,
    page: int = -1,
) -> pd.DataFrame:
    """
    基金拆分

    Args:
        year: 年份
        fund_type: 基金类型
        page: 页码,-1表示全部

    Returns:
        基金拆分数据 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_cf_em(year=year, typ=fund_type, page=page)
        if df.empty:
            raise DataNotFoundError("基金拆分数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金拆分数据失败: {e}") from e

get_fund_dividend_rank

get_fund_dividend_rank() -> pd.DataFrame

累计分红排行

返回:

类型 描述
DataFrame

累计分红排行 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_dividend_rank(self) -> pd.DataFrame:
    """
    累计分红排行

    Returns:
        累计分红排行 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_fh_rank_em()
        if df.empty:
            raise DataNotFoundError("基金累计分红排行数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金累计分红排行失败: {e}") from e

get_fund_rank_by_type

get_fund_rank_by_type(
    fund_type: str = "全部",
) -> pd.DataFrame

开放式基金排行

参数:

名称 类型 描述 默认
fund_type str

基金类型,可选:全部/股票型/混合型/债券型/指数型/QDII/LOF/FOF

'全部'

返回:

类型 描述
DataFrame

基金排行 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_rank_by_type(self, fund_type: str = "全部") -> pd.DataFrame:
    """
    开放式基金排行

    Args:
        fund_type: 基金类型,可选:全部/股票型/混合型/债券型/指数型/QDII/LOF/FOF

    Returns:
        基金排行 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_open_fund_rank_em(symbol=fund_type)
        if df.empty:
            raise DataNotFoundError(f"{fund_type}类型基金排行数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金排行失败: {e}") from e

get_exchange_fund_rank

get_exchange_fund_rank() -> pd.DataFrame

场内交易基金排行

返回:

类型 描述
DataFrame

场内基金排行 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_exchange_fund_rank(self) -> pd.DataFrame:
    """
    场内交易基金排行

    Returns:
        场内基金排行 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_exchange_rank_em()
        if df.empty:
            raise DataNotFoundError("场内交易基金排行数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取场内基金排行失败: {e}") from e

get_money_fund_rank

get_money_fund_rank() -> pd.DataFrame

货币型基金排行

返回:

类型 描述
DataFrame

货币基金排行 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_money_fund_rank(self) -> pd.DataFrame:
    """
    货币型基金排行

    Returns:
        货币基金排行 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_money_rank_em()
        if df.empty:
            raise DataNotFoundError("货币型基金排行数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取货币基金排行失败: {e}") from e

get_lcx_fund_rank

get_lcx_fund_rank() -> pd.DataFrame

理财基金排行

返回:

类型 描述
DataFrame

理财基金排行 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_lcx_fund_rank(self) -> pd.DataFrame:
    """
    理财基金排行

    Returns:
        理财基金排行 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_lcx_rank_em()
        if df.empty:
            raise DataNotFoundError("理财基金排行数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取理财基金排行失败: {e}") from e

get_hk_fund_rank

get_hk_fund_rank() -> pd.DataFrame

香港基金排行

返回:

类型 描述
DataFrame

香港基金排行 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_hk_fund_rank(self) -> pd.DataFrame:
    """
    香港基金排行

    Returns:
        香港基金排行 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_hk_rank_em()
        if df.empty:
            raise DataNotFoundError("香港基金排行数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取香港基金排行失败: {e}") from e

get_fund_achievement

get_fund_achievement(code: str) -> pd.DataFrame

基金业绩(年度+阶段)

参数:

名称 类型 描述 默认
code str

基金代码

必需

返回:

类型 描述
DataFrame

基金业绩数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_achievement(self, code: str) -> pd.DataFrame:
    """
    基金业绩(年度+阶段)

    Args:
        code: 基金代码

    Returns:
        基金业绩数据 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_individual_achievement_xq(symbol=code)
        if df.empty:
            raise DataNotFoundError(f"基金 {code} 业绩数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金业绩失败: {e}") from e

get_fund_risk_analysis

get_fund_risk_analysis(code: str) -> pd.DataFrame

基金数据分析(夏普/回撤)

参数:

名称 类型 描述 默认
code str

基金代码

必需

返回:

类型 描述
DataFrame

基金风险分析数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_risk_analysis(self, code: str) -> pd.DataFrame:
    """
    基金数据分析(夏普/回撤)

    Args:
        code: 基金代码

    Returns:
        基金风险分析数据 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_individual_analysis_xq(symbol=code)
        if df.empty:
            raise DataNotFoundError(f"基金 {code} 风险分析数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金风险分析失败: {e}") from e

get_fund_profit_probability

get_fund_profit_probability(code: str) -> pd.DataFrame

盈利概率

参数:

名称 类型 描述 默认
code str

基金代码

必需

返回:

类型 描述
DataFrame

盈利概率数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_profit_probability(self, code: str) -> pd.DataFrame:
    """
    盈利概率

    Args:
        code: 基金代码

    Returns:
        盈利概率数据 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_individual_profit_probability_xq(symbol=code)
        if df.empty:
            raise DataNotFoundError(f"基金 {code} 盈利概率数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金盈利概率失败: {e}") from e

get_fund_asset_allocation

get_fund_asset_allocation(
    code: str, date: str | None = None
) -> pd.DataFrame

基金资产配置

参数:

名称 类型 描述 默认
code str

基金代码

必需
date str | None

财报日期,格式 YYYYMMDD

None

返回:

类型 描述
DataFrame

资产配置数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_fund_asset_allocation(
    self, code: str, date: str | None = None
) -> pd.DataFrame:
    """
    基金资产配置

    Args:
        code: 基金代码
        date: 财报日期,格式 YYYYMMDD

    Returns:
        资产配置数据 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.fund_individual_detail_hold_xq(symbol=code, date=date)
        if df.empty:
            raise DataNotFoundError(f"基金 {code} 资产配置数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金资产配置失败: {e}") from e

get_index_spot_em

get_index_spot_em(category: str = '沪深重要指数') -> pd.DataFrame

东财指数实时行情

参数:

名称 类型 描述 默认
category str

指数类别,可选:沪深重要指数/上证系列指数/深证系列指数/中证系列指数

'沪深重要指数'

返回:

类型 描述
DataFrame

指数实时行情 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_index_spot_em(self, category: str = "沪深重要指数") -> pd.DataFrame:
    """
    东财指数实时行情

    Args:
        category: 指数类别,可选:沪深重要指数/上证系列指数/深证系列指数/中证系列指数

    Returns:
        指数实时行情 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.stock_zh_index_spot_em(symbol=category)
        if df.empty:
            raise DataNotFoundError(f"{category}指数实时行情数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取东财指数实时行情失败: {e}") from e

get_index_spot_sina

get_index_spot_sina() -> pd.DataFrame

新浪指数实时行情

返回:

类型 描述
DataFrame

指数实时行情 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_index_spot_sina(self) -> pd.DataFrame:
    """
    新浪指数实时行情

    Returns:
        指数实时行情 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.stock_zh_index_spot_sina()
        if df.empty:
            raise DataNotFoundError("新浪指数实时行情数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取新浪指数实时行情失败: {e}") from e

get_index_daily_tx

get_index_daily_tx(
    code: str,
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame

腾讯指数历史

参数:

名称 类型 描述 默认
code str

指数代码,如 "sh000001"

必需
start str | None

开始日期,格式 YYYYMMDD

None
end str | None

结束日期,格式 YYYYMMDD

None

返回:

类型 描述
DataFrame

指数历史数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_index_daily_tx(
    self, code: str, start: str | None = None, end: str | None = None
) -> pd.DataFrame:
    """
    腾讯指数历史

    Args:
        code: 指数代码,如 "sh000001"
        start: 开始日期,格式 YYYYMMDD
        end: 结束日期,格式 YYYYMMDD

    Returns:
        指数历史数据 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.stock_zh_index_daily_tx(symbol=code, start_date=start, end_date=end)
        if df.empty:
            raise DataNotFoundError(f"指数 {code} 历史数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取腾讯指数历史失败: {e}") from e

get_index_daily_em

get_index_daily_em(
    code: str,
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame

东财指数历史

参数:

名称 类型 描述 默认
code str

指数代码,如 "sz399552"

必需
start str | None

开始日期,格式 YYYYMMDD

None
end str | None

结束日期,格式 YYYYMMDD

None

返回:

类型 描述
DataFrame

指数历史数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_index_daily_em(
    self, code: str, start: str | None = None, end: str | None = None
) -> pd.DataFrame:
    """
    东财指数历史

    Args:
        code: 指数代码,如 "sz399552"
        start: 开始日期,格式 YYYYMMDD
        end: 结束日期,格式 YYYYMMDD

    Returns:
        指数历史数据 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.stock_zh_index_daily_em(symbol=code, start_date=start, end_date=end)
        if df.empty:
            raise DataNotFoundError(f"指数 {code} 历史数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取东财指数历史失败: {e}") from e

get_index_hist

get_index_hist(
    code: str,
    period: str = "daily",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame

指数通用历史

参数:

名称 类型 描述 默认
code str

指数代码

必需
period str

周期,可选:daily/weekly/monthly

'daily'
start str | None

开始日期,格式 YYYYMMDD

None
end str | None

结束日期,格式 YYYYMMDD

None

返回:

类型 描述
DataFrame

指数历史数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_index_hist(
    self,
    code: str,
    period: str = "daily",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame:
    """
    指数通用历史

    Args:
        code: 指数代码
        period: 周期,可选:daily/weekly/monthly
        start: 开始日期,格式 YYYYMMDD
        end: 结束日期,格式 YYYYMMDD

    Returns:
        指数历史数据 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.index_zh_a_hist(
            symbol=code, period=period, start_date=start, end_date=end
        )
        if df.empty:
            raise DataNotFoundError(f"指数 {code} 历史数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取指数通用历史失败: {e}") from e

get_index_minute

get_index_minute(
    code: str,
    period: str = "1",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame

指数分时

参数:

名称 类型 描述 默认
code str

指数代码

必需
period str

周期,可选:1/5/15/30/60

'1'
start str | None

开始日期,格式 YYYYMMDD

None
end str | None

结束日期,格式 YYYYMMDD

None

返回:

类型 描述
DataFrame

指数分时数据 DataFrame

源代码位于: src/fund_cli/data/adapters/akshare_adapter.py
def get_index_minute(
    self,
    code: str,
    period: str = "1",
    start: str | None = None,
    end: str | None = None,
) -> pd.DataFrame:
    """
    指数分时

    Args:
        code: 指数代码
        period: 周期,可选:1/5/15/30/60
        start: 开始日期,格式 YYYYMMDD
        end: 结束日期,格式 YYYYMMDD

    Returns:
        指数分时数据 DataFrame
    """
    ak = self._get_akshare()
    try:
        df = ak.index_zh_a_hist_min_em(
            symbol=code, period=period, start_date=start, end_date=end
        )
        if df.empty:
            raise DataNotFoundError(f"指数 {code} 分时数据不存在")
        return df.reset_index(drop=True)
    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取指数分时数据失败: {e}") from e

TushareAdapter

Bases: DataSourceAdapterMixin, DataSourceAdapter


              flowchart TD
              fund_cli.data.adapters.tushare_adapter.TushareAdapter[TushareAdapter]
              fund_cli.data.adapters.mixins.DataSourceAdapterMixin[DataSourceAdapterMixin]
              fund_cli.data.base.DataSourceAdapter[DataSourceAdapter]

                              fund_cli.data.adapters.mixins.DataSourceAdapterMixin --> fund_cli.data.adapters.tushare_adapter.TushareAdapter
                
                fund_cli.data.base.DataSourceAdapter --> fund_cli.data.adapters.tushare_adapter.TushareAdapter
                


              click fund_cli.data.adapters.tushare_adapter.TushareAdapter href "" "fund_cli.data.adapters.tushare_adapter.TushareAdapter"
              click fund_cli.data.adapters.mixins.DataSourceAdapterMixin href "" "fund_cli.data.adapters.mixins.DataSourceAdapterMixin"
              click fund_cli.data.base.DataSourceAdapter href "" "fund_cli.data.base.DataSourceAdapter"
            

Tushare 数据源适配器.

使用 Tushare Pro API 获取基金数据,特点: - 数据质量高 - 需要注册获取 Token - 部分接口需要积分

适配 Tushare 2025.11 变更: - 禁止多 ts_code 批量提取,改用 trade_date 批量拉取后本地过滤 - 尊重积分门槛制,根据用户积分等级自动调整请求频率

Note: 继承顺序 DataSourceAdapterMixin 在前,确保其方法在 MRO 中优先于 DataSourceAdapter 的抽象方法

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
 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
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
class TushareAdapter(DataSourceAdapterMixin, DataSourceAdapter):
    """
    Tushare 数据源适配器.

    使用 Tushare Pro API 获取基金数据,特点:
    - 数据质量高
    - 需要注册获取 Token
    - 部分接口需要积分

    适配 Tushare 2025.11 变更:
    - 禁止多 ts_code 批量提取,改用 trade_date 批量拉取后本地过滤
    - 尊重积分门槛制,根据用户积分等级自动调整请求频率

    Note: 继承顺序 DataSourceAdapterMixin 在前,确保其方法在 MRO 中优先于 DataSourceAdapter 的抽象方法
    """

    def __init__(self, cache: DataCache | None = None):
        """
        初始化 Tushare 适配器.

        Args:
            cache: 缓存管理器,可选
        """
        super().__init__("tushare")
        self._cache = cache
        self._ts = None
        self._token: str | None = None
        self._request_count = 0
        self._last_request_time = datetime.min
        self._min_interval = 1.0  # 最小请求间隔(秒)

    def _get_tushare(self):
        """延迟加载 Tushare."""
        if self._ts is None:
            try:
                import tushare as ts

                config = get_config()
                self._token = config.data.tushare_token
                if not self._token:
                    raise DataSourceError(
                        "Tushare Token 未配置,请在 .env 中设置 FUND_DATA_TUSHARE_TOKEN"
                    )
                ts.set_token(self._token)
                self._ts = ts.pro_api()
            except ImportError as e:
                raise DataSourceError("Tushare 未安装,请运行: pip install tushare") from e
        return self._ts

    def _rate_limit(self) -> None:
        """简单的请求频率限制."""
        now = datetime.now()
        elapsed = (now - self._last_request_time).total_seconds()
        if elapsed < self._min_interval:
            import time
            time.sleep(self._min_interval - elapsed)
        self._last_request_time = datetime.now()
        self._request_count += 1

    def _convert_date_format(self, df: pd.DataFrame, date_columns: list[str]) -> pd.DataFrame:
        """转换日期格式为 YYYY-MM-DD."""
        for col in date_columns:
            if col in df.columns:
                if df[col].dtype == 'object':
                    df[col] = pd.to_datetime(df[col], format='%Y%m%d', errors='coerce').dt.strftime('%Y-%m-%d')
                elif pd.api.types.is_datetime64_any_dtype(df[col]):
                    df[col] = df[col].dt.strftime('%Y-%m-%d')
        return df

    def is_available(self) -> bool:
        """检查 Tushare 是否可用."""
        try:
            config = get_config()
            return bool(config.data.tushare_token)
        except Exception:
            return False

    # =========================================================================
    # P0 - 核心基金功能接口 (18个)
    # =========================================================================

    # ----- 基金基本信息 (5个) -----

    def get_fund_info(self, fund_code: str) -> dict[str, Any]:
        """
        获取基金基础信息.

        Args:
            fund_code: 基金代码

        Returns:
            基金信息字典
        """
        if self._cache:
            cached = self._cache.get_fund_info(fund_code)
            if cached:
                return cached

        ts = self._get_tushare()
        self._rate_limit()

        try:
            df = ts.fund_basic(ts_code=f"{fund_code}.OF")

            if df.empty:
                raise DataNotFoundError(f"基金 {fund_code} 不存在")

            row = df.iloc[0]

            result = {
                "code": fund_code,
                "name": row.get("name", ""),
                "type": row.get("fund_type", "未知"),
                "establish_date": row.get("found_date", None),
                "manager": row.get("manager", ""),
                "company": row.get("management", ""),
                "scale": None,
            }

            if self._cache:
                self._cache.set_fund_info(fund_code, result)

            return result

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金信息失败: {e}") from e

    def get_all_fund_names(self) -> pd.DataFrame:
        """
        获取所有基金名称列表.

        Returns:
            DataFrame包含:基金代码, 拼音缩写, 基金简称, 基金类型, 拼音全称
        """
        ts = self._get_tushare()
        self._rate_limit()

        try:
            df = ts.fund_basic(market="O")

            df = df.rename(
                columns={
                    "ts_code": "code",
                    "name": "name",
                    "fullname": "full_name",
                    "symbol": "symbol",
                    "fund_type": "type",
                }
            )

            df = df[["code", "symbol", "name", "type", "full_name"]]

            return df.reset_index(drop=True)

        except Exception as e:
            raise DataSourceError(f"获取基金名称列表失败: {e}") from e

    def get_fund_info_ths(self, fund_code: str) -> dict[str, Any]:
        """
        同花顺-基金基本信息.

        Args:
            fund_code: 基金代码

        Returns:
            基金详细信息字典
        """
        ts = self._get_tushare()
        self._rate_limit()

        try:
            df = ts.fund_basic(ts_code=f"{fund_code}.OF")

            if df.empty:
                raise DataNotFoundError(f"基金 {fund_code} 不存在")

            row = df.iloc[0]

            return {
                "code": fund_code,
                "name": row.get("name", ""),
                "full_name": row.get("fullname", ""),
                "type": row.get("fund_type", ""),
                "management": row.get("management", ""),
                "trustee": row.get("trustee", ""),
                "found_date": row.get("found_date", ""),
                "due_date": row.get("due_date", ""),
                "list_date": row.get("list_date", ""),
                "issue_date": row.get("issue_date", ""),
                "delist_date": row.get("delist_date", ""),
                "status": row.get("status", ""),
            }

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取同花顺基金信息失败: {e}") from e

    def get_index_fund_info(
        self, category: str = "全部", indicator: str = "全部"
    ) -> pd.DataFrame:
        """
        东方财富-指数型基金基本信息.

        Args:
            category: 分类,可选"全部","沪深指数","行业主题","大盘指数"等
            indicator: 指标,可选"全部","被动指数型","增强指数型"

        Returns:
            指数型基金信息DataFrame
        """
        ts = self._get_tushare()
        self._rate_limit()

        try:
            # Tushare fund_basic 不支持分类过滤,返回全部后本地过滤
            df = ts.fund_basic(market="O")

            # 过滤指数型基金
            if category != "全部":
                df = df[df["invest_type"].str.contains("指数", na=False)]

            df = df.rename(
                columns={
                    "ts_code": "code",
                    "name": "name",
                    "fund_type": "type",
                    "invest_type": "invest_type",
                    "management": "management",
                    "found_date": "found_date",
                }
            )

            return df[["code", "name", "type", "invest_type", "management", "found_date"]]

        except Exception as e:
            raise DataSourceError(f"获取指数基金信息失败: {e}") from e

    def get_fund_overview(self, fund_code: str) -> dict[str, Any]:
        """
        天天基金-基金档案基本概况.

        Args:
            fund_code: 基金代码

        Returns:
            基金概况字典
        """
        # 使用 fund_basic 获取基本信息
        ts = self._get_tushare()
        self._rate_limit()

        try:
            df = ts.fund_basic(ts_code=f"{fund_code}.OF")

            if df.empty:
                raise DataNotFoundError(f"基金 {fund_code} 不存在")

            row = df.iloc[0]

            return {
                "code": fund_code,
                "name": row.get("name", ""),
                "type": row.get("fund_type", ""),
                "management": row.get("management", ""),
                "trustee": row.get("trustee", ""),
                "found_date": row.get("found_date", ""),
                "scale": None,
                "status": row.get("status", ""),
            }

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金概况失败: {e}") from e

    # ----- 基金申购状态 (1个) -----

    def get_fund_purchase_status(self) -> pd.DataFrame:
        """
        东方财富-基金申购/赎回状态.

        Returns:
            DataFrame包含:基金代码, 基金简称, 申购状态, 赎回状态等
        """
        ts = self._get_tushare()
        self._rate_limit()

        try:
            df = ts.fund_basic(market="O")

            # fund_basic 包含状态字段
            df = df.rename(
                columns={
                    "ts_code": "code",
                    "name": "name",
                    "status": "purchase_status",
                }
            )

            return df[["code", "name", "purchase_status"]]

        except Exception as e:
            raise DataSourceError(f"获取基金申购状态失败: {e}") from e

    # ----- 基金净值数据 (2个) -----

    def get_fund_nav(
        self,
        fund_code: str,
        start_date: date | None = None,
        end_date: date | None = None,
    ) -> pd.DataFrame:
        """
        获取基金净值数据.

        Args:
            fund_code: 基金代码
            start_date: 开始日期
            end_date: 结束日期

        Returns:
            净值数据DataFrame
        """
        ts = self._get_tushare()
        self._rate_limit()

        try:
            # Tushare 2025.11 变更:使用 trade_date 批量拉取后本地过滤
            df = ts.fund_nav(
                ts_code=f"{fund_code}.OF",
                start_date=start_date.strftime("%Y%m%d") if start_date else None,
                end_date=end_date.strftime("%Y%m%d") if end_date else None,
            )

            if df.empty:
                raise DataNotFoundError(f"基金 {fund_code} 净值数据不存在")

            # 转换日期格式
            df = self._convert_date_format(df, ["end_date"])

            df = df.rename(
                columns={
                    "end_date": "nav_date",
                    "unit_nav": "unit_nav",
                    "accum_nav": "accumulated_nav",
                }
            )

            df["fund_code"] = fund_code

            if "daily_return" not in df.columns:
                df["daily_return"] = df["unit_nav"].pct_change() * 100

            result_df = df[["fund_code", "nav_date", "unit_nav", "accumulated_nav", "daily_return"]].copy()
            return result_df.sort_values("nav_date").reset_index(drop=True)

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金净值失败: {e}") from e

    def get_fund_daily_nav(self) -> pd.DataFrame:
        """
        东方财富-开放式基金每日净值(全部).

        Returns:
            全部基金净值DataFrame
        """
        ts = self._get_tushare()
        self._rate_limit()

        try:
            df = ts.fund_nav()

            if df.empty:
                raise DataNotFoundError("基金净值数据不存在")

            # 转换日期格式
            df = self._convert_date_format(df, ["end_date"])

            df = df.rename(
                columns={
                    "ts_code": "fund_code",
                    "end_date": "nav_date",
                    "unit_nav": "unit_nav",
                    "accum_nav": "accumulated_nav",
                }
            )

            # 提取基金代码
            df["fund_code"] = df["fund_code"].str.replace(".OF", "", regex=False)

            return df[["fund_code", "nav_date", "unit_nav", "accumulated_nav"]].sort_values(
                ["fund_code", "nav_date"]
            ).reset_index(drop=True)

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金每日净值失败: {e}") from e

    # ----- 基金行情数据 (8个) -----

    def get_etf_spot(self) -> pd.DataFrame:
        """
        东方财富-ETF实时行情(全部).

        Returns:
            ETF实时行情DataFrame
        """
        ts = self._get_tushare()
        self._rate_limit()

        try:
            df = ts.fund_basic(market="O", status="L")

            # 过滤 ETF
            df = df[df["fund_type"].str.contains("ETF", na=False)]

            df = df.rename(
                columns={
                    "ts_code": "code",
                    "name": "name",
                    "fund_type": "type",
                    "management": "management",
                }
            )

            return df[["code", "name", "type", "management"]]

        except Exception as e:
            raise DataSourceError(f"获取ETF实时行情失败: {e}") from e

    def get_fund_category_spot(
        self, category: str = "", date: str | None = None
    ) -> pd.DataFrame:
        """
        同花顺-基金实时行情(按类型).

        Args:
            category: 基金类型,如"股票型","债券型","混合型","ETF","LOF"等
            date: 日期

        Returns:
            基金行情DataFrame
        """
        ts = self._get_tushare()
        self._rate_limit()

        try:
            df = ts.fund_basic(market="O", status="L")

            if category:
                df = df[df["fund_type"].str.contains(category, na=False)]

            df = df.rename(
                columns={
                    "ts_code": "code",
                    "name": "name",
                    "fund_type": "type",
                }
            )

            return df[["code", "name", "type"]]

        except Exception as e:
            raise DataSourceError(f"获取基金分类行情失败: {e}") from e

    def get_etf_spot_ths(self, date: str | None = None) -> pd.DataFrame:
        """
        同花顺-ETF实时行情.

        Args:
            date: 日期

        Returns:
            ETF行情DataFrame
        """
        return self.get_etf_spot()

    def get_lof_spot(self) -> pd.DataFrame:
        """
        东方财富-LOF实时行情(全部).

        Returns:
            LOF实时行情DataFrame
        """
        ts = self._get_tushare()
        self._rate_limit()

        try:
            df = ts.fund_basic(market="O", status="L")

            # 过滤 LOF
            df = df[df["fund_type"].str.contains("LOF", na=False)]

            df = df.rename(
                columns={
                    "ts_code": "code",
                    "name": "name",
                    "fund_type": "type",
                    "management": "management",
                }
            )

            return df[["code", "name", "type", "management"]]

        except Exception as e:
            raise DataSourceError(f"获取LOF实时行情失败: {e}") from e

    def get_etf_hist(
        self,
        fund_code: str,
        period: str = "daily",
        start_date: str | None = None,
        end_date: str | None = None,
    ) -> pd.DataFrame:
        """
        东方财富-ETF历史行情.

        Args:
            fund_code: 基金代码
            period: 周期,"daily","weekly","monthly"
            start_date: 开始日期
            end_date: 结束日期

        Returns:
            ETF历史行情DataFrame
        """
        ts = self._get_tushare()
        self._rate_limit()

        try:
            # 转换周期(保留以保持逻辑清晰)
            _freq = "D" if period == "daily" else ("W" if period == "weekly" else "M")

            df = ts.fund_nav(
                ts_code=f"{fund_code}.OF",
                start_date=start_date.replace("-", "") if start_date else None,
                end_date=end_date.replace("-", "") if end_date else None,
            )

            if df.empty:
                raise DataNotFoundError(f"ETF {fund_code} 历史行情不存在")

            df = self._convert_date_format(df, ["end_date"])

            df = df.rename(
                columns={
                    "end_date": "nav_date",
                    "unit_nav": "close",
                    "accum_nav": "accumulated_nav",
                }
            )

            df["fund_code"] = fund_code
            df["volume"] = 0

            return df[["fund_code", "nav_date", "close", "volume", "accumulated_nav"]].sort_values(
                "nav_date"
            ).reset_index(drop=True)

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取ETF历史行情失败: {e}") from e

    def get_lof_hist(
        self,
        fund_code: str,
        period: str = "daily",
        start_date: str | None = None,
        end_date: str | None = None,
    ) -> pd.DataFrame:
        """
        东方财富-LOF历史行情.

        Args:
            fund_code: 基金代码
            period: 周期
            start_date: 开始日期
            end_date: 结束日期

        Returns:
            LOF历史行情DataFrame
        """
        return self.get_etf_hist(fund_code, period, start_date, end_date)

    # ----- 基金经理和持仓数据 (3个) -----

    def get_fund_manager(self, fund_code: str) -> pd.DataFrame:
        """
        获取基金经理信息.

        Args:
            fund_code: 基金代码

        Returns:
            基金经理信息DataFrame
        """
        ts = self._get_tushare()
        self._rate_limit()

        try:
            df = ts.fund_manager(ts_code=f"{fund_code}.OF")

            if df.empty:
                raise DataNotFoundError(f"基金 {fund_code} 基金经理信息不存在")

            df = df.rename(
                columns={
                    "ts_code": "fund_code",
                    "name": "manager_name",
                    "start_date": "start_date",
                    "end_date": "end_date",
                }
            )

            df["fund_code"] = fund_code

            return df[["fund_code", "manager_name", "start_date", "end_date"]]

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金经理信息失败: {e}") from e

    def get_fund_holdings(
        self, fund_code: str, date: str | None = None
    ) -> pd.DataFrame:
        """
        获取基金持仓数据.

        Args:
            fund_code: 基金代码
            date: 报告期

        Returns:
            基金持仓DataFrame
        """
        ts = self._get_tushare()
        self._rate_limit()

        try:
            df = ts.fund_portfolio(ts_code=f"{fund_code}.OF")

            if df.empty:
                raise DataNotFoundError(f"基金 {fund_code} 持仓数据不存在")

            df = df.rename(
                columns={
                    "ts_code": "fund_code",
                    "symbol": "stock_code",
                    "name": "stock_name",
                    "vol": "volume",
                    "proportions": "proportion",
                }
            )

            df["fund_code"] = fund_code

            return df[["fund_code", "stock_code", "stock_name", "volume", "proportion"]]

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金持仓失败: {e}") from e

    def get_fund_asset_allocation(self, fund_code: str) -> dict[str, Any]:
        """
        获取基金资产配置.

        Args:
            fund_code: 基金代码

        Returns:
            资产配置字典
        """
        ts = self._get_tushare()
        self._rate_limit()

        try:
            df = ts.fund_asset()

            if df.empty:
                raise DataNotFoundError(f"基金 {fund_code} 资产配置数据不存在")

            # 过滤指定基金
            df = df[df["ts_code"] == f"{fund_code}.OF"]

            if df.empty:
                raise DataNotFoundError(f"基金 {fund_code} 资产配置数据不存在")

            row = df.iloc[0]

            return {
                "fund_code": fund_code,
                "date": row.get("report_date", ""),
                "stock_ratio": row.get("stock_ratio", 0),
                "bond_ratio": row.get("bond_ratio", 0),
                "cash_ratio": row.get("cash_ratio", 0),
                "total_asset": row.get("total_asset", 0),
            }

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基金资产配置失败: {e}") from e

    def get_fund_benchmark(self, fund_code: str) -> dict[str, Any]:
        """
        获取基金业绩比较基准.

        Args:
            fund_code: 基金代码

        Returns:
            业绩比较基准字典
        """
        # 获取基金基本信息
        info = self.get_fund_info(fund_code)

        return {
            "fund_code": fund_code,
            "benchmark": info.get("benchmark", "未知"),
        }

    # =========================================================================
    # P1 - 分析增强功能接口 (部分实现)
    # =========================================================================

    def search_funds(
        self,
        fund_type: str | None = None,
        company: str | None = None,
        min_scale: float | None = None,
        max_scale: float | None = None,
        keyword: str | None = None,
        limit: int = 100,
    ) -> pd.DataFrame:
        """搜索基金."""
        ts = self._get_tushare()
        self._rate_limit()

        try:
            df = ts.fund_basic(market="O")

            if fund_type:
                df = df[df["fund_type"].str.contains(fund_type, na=False)]
            if company:
                df = df[df["management"].str.contains(company, na=False)]
            if keyword:
                mask = df["ts_code"].str.contains(keyword, na=False) | df["name"].str.contains(keyword, na=False)
                df = df[mask]

            df = df.head(limit)

            df = df.rename(
                columns={
                    "ts_code": "code",
                    "name": "name",
                    "fund_type": "type",
                    "management": "company",
                }
            )

            return df.reset_index(drop=True)

        except Exception as e:
            raise DataSourceError(f"搜索基金失败: {e}") from e

    def get_fund_list(self, fund_type: str | None = None) -> pd.DataFrame:
        """获取基金列表."""
        return self.search_funds(fund_type=fund_type)

    def get_benchmark_nav(
        self,
        benchmark_code: str,
        start_date: date | None = None,
        end_date: date | None = None,
    ) -> pd.DataFrame:
        """获取基准指数数据."""
        ts = self._get_tushare()
        self._rate_limit()

        try:
            # 转换指数代码格式
            if benchmark_code.startswith("0") or benchmark_code.startswith("1"):
                ts_code = f"{benchmark_code}.SH"
            else:
                ts_code = f"{benchmark_code}.SZ"

            df = ts.index_daily(
                ts_code=ts_code,
                start_date=start_date.strftime("%Y%m%d") if start_date else None,
                end_date=end_date.strftime("%Y%m%d") if end_date else None,
            )

            if df.empty:
                raise DataNotFoundError(f"指数 {benchmark_code} 不存在")

            df = self._convert_date_format(df, ["trade_date"])

            df = df.rename(
                columns={
                    "trade_date": "nav_date",
                    "close": "unit_nav",
                }
            )

            df["fund_code"] = benchmark_code
            df["daily_return"] = df["unit_nav"].pct_change() * 100
            df["accumulated_nav"] = df["unit_nav"]

            result_df = df[["fund_code", "nav_date", "unit_nav", "accumulated_nav", "daily_return"]].copy()
            return result_df.sort_values("nav_date").reset_index(drop=True)

        except DataNotFoundError:
            raise
        except Exception as e:
            raise DataSourceError(f"获取基准数据失败: {e}") from e

__init__

__init__(cache: DataCache | None = None)

初始化 Tushare 适配器.

参数:

名称 类型 描述 默认
cache DataCache | None

缓存管理器,可选

None
源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def __init__(self, cache: DataCache | None = None):
    """
    初始化 Tushare 适配器.

    Args:
        cache: 缓存管理器,可选
    """
    super().__init__("tushare")
    self._cache = cache
    self._ts = None
    self._token: str | None = None
    self._request_count = 0
    self._last_request_time = datetime.min
    self._min_interval = 1.0  # 最小请求间隔(秒)

is_available

is_available() -> bool

检查 Tushare 是否可用.

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def is_available(self) -> bool:
    """检查 Tushare 是否可用."""
    try:
        config = get_config()
        return bool(config.data.tushare_token)
    except Exception:
        return False

get_fund_info

get_fund_info(fund_code: str) -> dict[str, Any]

获取基金基础信息.

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需

返回:

类型 描述
dict[str, Any]

基金信息字典

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_fund_info(self, fund_code: str) -> dict[str, Any]:
    """
    获取基金基础信息.

    Args:
        fund_code: 基金代码

    Returns:
        基金信息字典
    """
    if self._cache:
        cached = self._cache.get_fund_info(fund_code)
        if cached:
            return cached

    ts = self._get_tushare()
    self._rate_limit()

    try:
        df = ts.fund_basic(ts_code=f"{fund_code}.OF")

        if df.empty:
            raise DataNotFoundError(f"基金 {fund_code} 不存在")

        row = df.iloc[0]

        result = {
            "code": fund_code,
            "name": row.get("name", ""),
            "type": row.get("fund_type", "未知"),
            "establish_date": row.get("found_date", None),
            "manager": row.get("manager", ""),
            "company": row.get("management", ""),
            "scale": None,
        }

        if self._cache:
            self._cache.set_fund_info(fund_code, result)

        return result

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金信息失败: {e}") from e

get_all_fund_names

get_all_fund_names() -> pd.DataFrame

获取所有基金名称列表.

返回:

类型 描述
DataFrame

DataFrame包含:基金代码, 拼音缩写, 基金简称, 基金类型, 拼音全称

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_all_fund_names(self) -> pd.DataFrame:
    """
    获取所有基金名称列表.

    Returns:
        DataFrame包含:基金代码, 拼音缩写, 基金简称, 基金类型, 拼音全称
    """
    ts = self._get_tushare()
    self._rate_limit()

    try:
        df = ts.fund_basic(market="O")

        df = df.rename(
            columns={
                "ts_code": "code",
                "name": "name",
                "fullname": "full_name",
                "symbol": "symbol",
                "fund_type": "type",
            }
        )

        df = df[["code", "symbol", "name", "type", "full_name"]]

        return df.reset_index(drop=True)

    except Exception as e:
        raise DataSourceError(f"获取基金名称列表失败: {e}") from e

get_fund_info_ths

get_fund_info_ths(fund_code: str) -> dict[str, Any]

同花顺-基金基本信息.

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需

返回:

类型 描述
dict[str, Any]

基金详细信息字典

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_fund_info_ths(self, fund_code: str) -> dict[str, Any]:
    """
    同花顺-基金基本信息.

    Args:
        fund_code: 基金代码

    Returns:
        基金详细信息字典
    """
    ts = self._get_tushare()
    self._rate_limit()

    try:
        df = ts.fund_basic(ts_code=f"{fund_code}.OF")

        if df.empty:
            raise DataNotFoundError(f"基金 {fund_code} 不存在")

        row = df.iloc[0]

        return {
            "code": fund_code,
            "name": row.get("name", ""),
            "full_name": row.get("fullname", ""),
            "type": row.get("fund_type", ""),
            "management": row.get("management", ""),
            "trustee": row.get("trustee", ""),
            "found_date": row.get("found_date", ""),
            "due_date": row.get("due_date", ""),
            "list_date": row.get("list_date", ""),
            "issue_date": row.get("issue_date", ""),
            "delist_date": row.get("delist_date", ""),
            "status": row.get("status", ""),
        }

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取同花顺基金信息失败: {e}") from e

get_index_fund_info

get_index_fund_info(
    category: str = "全部", indicator: str = "全部"
) -> pd.DataFrame

东方财富-指数型基金基本信息.

参数:

名称 类型 描述 默认
category str

分类,可选"全部","沪深指数","行业主题","大盘指数"等

'全部'
indicator str

指标,可选"全部","被动指数型","增强指数型"

'全部'

返回:

类型 描述
DataFrame

指数型基金信息DataFrame

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_index_fund_info(
    self, category: str = "全部", indicator: str = "全部"
) -> pd.DataFrame:
    """
    东方财富-指数型基金基本信息.

    Args:
        category: 分类,可选"全部","沪深指数","行业主题","大盘指数"等
        indicator: 指标,可选"全部","被动指数型","增强指数型"

    Returns:
        指数型基金信息DataFrame
    """
    ts = self._get_tushare()
    self._rate_limit()

    try:
        # Tushare fund_basic 不支持分类过滤,返回全部后本地过滤
        df = ts.fund_basic(market="O")

        # 过滤指数型基金
        if category != "全部":
            df = df[df["invest_type"].str.contains("指数", na=False)]

        df = df.rename(
            columns={
                "ts_code": "code",
                "name": "name",
                "fund_type": "type",
                "invest_type": "invest_type",
                "management": "management",
                "found_date": "found_date",
            }
        )

        return df[["code", "name", "type", "invest_type", "management", "found_date"]]

    except Exception as e:
        raise DataSourceError(f"获取指数基金信息失败: {e}") from e

get_fund_overview

get_fund_overview(fund_code: str) -> dict[str, Any]

天天基金-基金档案基本概况.

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需

返回:

类型 描述
dict[str, Any]

基金概况字典

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_fund_overview(self, fund_code: str) -> dict[str, Any]:
    """
    天天基金-基金档案基本概况.

    Args:
        fund_code: 基金代码

    Returns:
        基金概况字典
    """
    # 使用 fund_basic 获取基本信息
    ts = self._get_tushare()
    self._rate_limit()

    try:
        df = ts.fund_basic(ts_code=f"{fund_code}.OF")

        if df.empty:
            raise DataNotFoundError(f"基金 {fund_code} 不存在")

        row = df.iloc[0]

        return {
            "code": fund_code,
            "name": row.get("name", ""),
            "type": row.get("fund_type", ""),
            "management": row.get("management", ""),
            "trustee": row.get("trustee", ""),
            "found_date": row.get("found_date", ""),
            "scale": None,
            "status": row.get("status", ""),
        }

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金概况失败: {e}") from e

get_fund_purchase_status

get_fund_purchase_status() -> pd.DataFrame

东方财富-基金申购/赎回状态.

返回:

类型 描述
DataFrame

DataFrame包含:基金代码, 基金简称, 申购状态, 赎回状态等

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_fund_purchase_status(self) -> pd.DataFrame:
    """
    东方财富-基金申购/赎回状态.

    Returns:
        DataFrame包含:基金代码, 基金简称, 申购状态, 赎回状态等
    """
    ts = self._get_tushare()
    self._rate_limit()

    try:
        df = ts.fund_basic(market="O")

        # fund_basic 包含状态字段
        df = df.rename(
            columns={
                "ts_code": "code",
                "name": "name",
                "status": "purchase_status",
            }
        )

        return df[["code", "name", "purchase_status"]]

    except Exception as e:
        raise DataSourceError(f"获取基金申购状态失败: {e}") from e

get_fund_nav

get_fund_nav(
    fund_code: str,
    start_date: date | None = None,
    end_date: date | None = None,
) -> pd.DataFrame

获取基金净值数据.

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需
start_date date | None

开始日期

None
end_date date | None

结束日期

None

返回:

类型 描述
DataFrame

净值数据DataFrame

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_fund_nav(
    self,
    fund_code: str,
    start_date: date | None = None,
    end_date: date | None = None,
) -> pd.DataFrame:
    """
    获取基金净值数据.

    Args:
        fund_code: 基金代码
        start_date: 开始日期
        end_date: 结束日期

    Returns:
        净值数据DataFrame
    """
    ts = self._get_tushare()
    self._rate_limit()

    try:
        # Tushare 2025.11 变更:使用 trade_date 批量拉取后本地过滤
        df = ts.fund_nav(
            ts_code=f"{fund_code}.OF",
            start_date=start_date.strftime("%Y%m%d") if start_date else None,
            end_date=end_date.strftime("%Y%m%d") if end_date else None,
        )

        if df.empty:
            raise DataNotFoundError(f"基金 {fund_code} 净值数据不存在")

        # 转换日期格式
        df = self._convert_date_format(df, ["end_date"])

        df = df.rename(
            columns={
                "end_date": "nav_date",
                "unit_nav": "unit_nav",
                "accum_nav": "accumulated_nav",
            }
        )

        df["fund_code"] = fund_code

        if "daily_return" not in df.columns:
            df["daily_return"] = df["unit_nav"].pct_change() * 100

        result_df = df[["fund_code", "nav_date", "unit_nav", "accumulated_nav", "daily_return"]].copy()
        return result_df.sort_values("nav_date").reset_index(drop=True)

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金净值失败: {e}") from e

get_fund_daily_nav

get_fund_daily_nav() -> pd.DataFrame

东方财富-开放式基金每日净值(全部).

返回:

类型 描述
DataFrame

全部基金净值DataFrame

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_fund_daily_nav(self) -> pd.DataFrame:
    """
    东方财富-开放式基金每日净值(全部).

    Returns:
        全部基金净值DataFrame
    """
    ts = self._get_tushare()
    self._rate_limit()

    try:
        df = ts.fund_nav()

        if df.empty:
            raise DataNotFoundError("基金净值数据不存在")

        # 转换日期格式
        df = self._convert_date_format(df, ["end_date"])

        df = df.rename(
            columns={
                "ts_code": "fund_code",
                "end_date": "nav_date",
                "unit_nav": "unit_nav",
                "accum_nav": "accumulated_nav",
            }
        )

        # 提取基金代码
        df["fund_code"] = df["fund_code"].str.replace(".OF", "", regex=False)

        return df[["fund_code", "nav_date", "unit_nav", "accumulated_nav"]].sort_values(
            ["fund_code", "nav_date"]
        ).reset_index(drop=True)

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金每日净值失败: {e}") from e

get_etf_spot

get_etf_spot() -> pd.DataFrame

东方财富-ETF实时行情(全部).

返回:

类型 描述
DataFrame

ETF实时行情DataFrame

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_etf_spot(self) -> pd.DataFrame:
    """
    东方财富-ETF实时行情(全部).

    Returns:
        ETF实时行情DataFrame
    """
    ts = self._get_tushare()
    self._rate_limit()

    try:
        df = ts.fund_basic(market="O", status="L")

        # 过滤 ETF
        df = df[df["fund_type"].str.contains("ETF", na=False)]

        df = df.rename(
            columns={
                "ts_code": "code",
                "name": "name",
                "fund_type": "type",
                "management": "management",
            }
        )

        return df[["code", "name", "type", "management"]]

    except Exception as e:
        raise DataSourceError(f"获取ETF实时行情失败: {e}") from e

get_fund_category_spot

get_fund_category_spot(
    category: str = "", date: str | None = None
) -> pd.DataFrame

同花顺-基金实时行情(按类型).

参数:

名称 类型 描述 默认
category str

基金类型,如"股票型","债券型","混合型","ETF","LOF"等

''
date str | None

日期

None

返回:

类型 描述
DataFrame

基金行情DataFrame

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_fund_category_spot(
    self, category: str = "", date: str | None = None
) -> pd.DataFrame:
    """
    同花顺-基金实时行情(按类型).

    Args:
        category: 基金类型,如"股票型","债券型","混合型","ETF","LOF"等
        date: 日期

    Returns:
        基金行情DataFrame
    """
    ts = self._get_tushare()
    self._rate_limit()

    try:
        df = ts.fund_basic(market="O", status="L")

        if category:
            df = df[df["fund_type"].str.contains(category, na=False)]

        df = df.rename(
            columns={
                "ts_code": "code",
                "name": "name",
                "fund_type": "type",
            }
        )

        return df[["code", "name", "type"]]

    except Exception as e:
        raise DataSourceError(f"获取基金分类行情失败: {e}") from e

get_etf_spot_ths

get_etf_spot_ths(date: str | None = None) -> pd.DataFrame

同花顺-ETF实时行情.

参数:

名称 类型 描述 默认
date str | None

日期

None

返回:

类型 描述
DataFrame

ETF行情DataFrame

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_etf_spot_ths(self, date: str | None = None) -> pd.DataFrame:
    """
    同花顺-ETF实时行情.

    Args:
        date: 日期

    Returns:
        ETF行情DataFrame
    """
    return self.get_etf_spot()

get_lof_spot

get_lof_spot() -> pd.DataFrame

东方财富-LOF实时行情(全部).

返回:

类型 描述
DataFrame

LOF实时行情DataFrame

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_lof_spot(self) -> pd.DataFrame:
    """
    东方财富-LOF实时行情(全部).

    Returns:
        LOF实时行情DataFrame
    """
    ts = self._get_tushare()
    self._rate_limit()

    try:
        df = ts.fund_basic(market="O", status="L")

        # 过滤 LOF
        df = df[df["fund_type"].str.contains("LOF", na=False)]

        df = df.rename(
            columns={
                "ts_code": "code",
                "name": "name",
                "fund_type": "type",
                "management": "management",
            }
        )

        return df[["code", "name", "type", "management"]]

    except Exception as e:
        raise DataSourceError(f"获取LOF实时行情失败: {e}") from e

get_etf_hist

get_etf_hist(
    fund_code: str,
    period: str = "daily",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame

东方财富-ETF历史行情.

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需
period str

周期,"daily","weekly","monthly"

'daily'
start_date str | None

开始日期

None
end_date str | None

结束日期

None

返回:

类型 描述
DataFrame

ETF历史行情DataFrame

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_etf_hist(
    self,
    fund_code: str,
    period: str = "daily",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame:
    """
    东方财富-ETF历史行情.

    Args:
        fund_code: 基金代码
        period: 周期,"daily","weekly","monthly"
        start_date: 开始日期
        end_date: 结束日期

    Returns:
        ETF历史行情DataFrame
    """
    ts = self._get_tushare()
    self._rate_limit()

    try:
        # 转换周期(保留以保持逻辑清晰)
        _freq = "D" if period == "daily" else ("W" if period == "weekly" else "M")

        df = ts.fund_nav(
            ts_code=f"{fund_code}.OF",
            start_date=start_date.replace("-", "") if start_date else None,
            end_date=end_date.replace("-", "") if end_date else None,
        )

        if df.empty:
            raise DataNotFoundError(f"ETF {fund_code} 历史行情不存在")

        df = self._convert_date_format(df, ["end_date"])

        df = df.rename(
            columns={
                "end_date": "nav_date",
                "unit_nav": "close",
                "accum_nav": "accumulated_nav",
            }
        )

        df["fund_code"] = fund_code
        df["volume"] = 0

        return df[["fund_code", "nav_date", "close", "volume", "accumulated_nav"]].sort_values(
            "nav_date"
        ).reset_index(drop=True)

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取ETF历史行情失败: {e}") from e

get_lof_hist

get_lof_hist(
    fund_code: str,
    period: str = "daily",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame

东方财富-LOF历史行情.

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需
period str

周期

'daily'
start_date str | None

开始日期

None
end_date str | None

结束日期

None

返回:

类型 描述
DataFrame

LOF历史行情DataFrame

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_lof_hist(
    self,
    fund_code: str,
    period: str = "daily",
    start_date: str | None = None,
    end_date: str | None = None,
) -> pd.DataFrame:
    """
    东方财富-LOF历史行情.

    Args:
        fund_code: 基金代码
        period: 周期
        start_date: 开始日期
        end_date: 结束日期

    Returns:
        LOF历史行情DataFrame
    """
    return self.get_etf_hist(fund_code, period, start_date, end_date)

get_fund_manager

get_fund_manager(fund_code: str) -> pd.DataFrame

获取基金经理信息.

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需

返回:

类型 描述
DataFrame

基金经理信息DataFrame

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_fund_manager(self, fund_code: str) -> pd.DataFrame:
    """
    获取基金经理信息.

    Args:
        fund_code: 基金代码

    Returns:
        基金经理信息DataFrame
    """
    ts = self._get_tushare()
    self._rate_limit()

    try:
        df = ts.fund_manager(ts_code=f"{fund_code}.OF")

        if df.empty:
            raise DataNotFoundError(f"基金 {fund_code} 基金经理信息不存在")

        df = df.rename(
            columns={
                "ts_code": "fund_code",
                "name": "manager_name",
                "start_date": "start_date",
                "end_date": "end_date",
            }
        )

        df["fund_code"] = fund_code

        return df[["fund_code", "manager_name", "start_date", "end_date"]]

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金经理信息失败: {e}") from e

get_fund_holdings

get_fund_holdings(
    fund_code: str, date: str | None = None
) -> pd.DataFrame

获取基金持仓数据.

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需
date str | None

报告期

None

返回:

类型 描述
DataFrame

基金持仓DataFrame

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_fund_holdings(
    self, fund_code: str, date: str | None = None
) -> pd.DataFrame:
    """
    获取基金持仓数据.

    Args:
        fund_code: 基金代码
        date: 报告期

    Returns:
        基金持仓DataFrame
    """
    ts = self._get_tushare()
    self._rate_limit()

    try:
        df = ts.fund_portfolio(ts_code=f"{fund_code}.OF")

        if df.empty:
            raise DataNotFoundError(f"基金 {fund_code} 持仓数据不存在")

        df = df.rename(
            columns={
                "ts_code": "fund_code",
                "symbol": "stock_code",
                "name": "stock_name",
                "vol": "volume",
                "proportions": "proportion",
            }
        )

        df["fund_code"] = fund_code

        return df[["fund_code", "stock_code", "stock_name", "volume", "proportion"]]

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金持仓失败: {e}") from e

get_fund_asset_allocation

get_fund_asset_allocation(fund_code: str) -> dict[str, Any]

获取基金资产配置.

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需

返回:

类型 描述
dict[str, Any]

资产配置字典

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_fund_asset_allocation(self, fund_code: str) -> dict[str, Any]:
    """
    获取基金资产配置.

    Args:
        fund_code: 基金代码

    Returns:
        资产配置字典
    """
    ts = self._get_tushare()
    self._rate_limit()

    try:
        df = ts.fund_asset()

        if df.empty:
            raise DataNotFoundError(f"基金 {fund_code} 资产配置数据不存在")

        # 过滤指定基金
        df = df[df["ts_code"] == f"{fund_code}.OF"]

        if df.empty:
            raise DataNotFoundError(f"基金 {fund_code} 资产配置数据不存在")

        row = df.iloc[0]

        return {
            "fund_code": fund_code,
            "date": row.get("report_date", ""),
            "stock_ratio": row.get("stock_ratio", 0),
            "bond_ratio": row.get("bond_ratio", 0),
            "cash_ratio": row.get("cash_ratio", 0),
            "total_asset": row.get("total_asset", 0),
        }

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基金资产配置失败: {e}") from e

get_fund_benchmark

get_fund_benchmark(fund_code: str) -> dict[str, Any]

获取基金业绩比较基准.

参数:

名称 类型 描述 默认
fund_code str

基金代码

必需

返回:

类型 描述
dict[str, Any]

业绩比较基准字典

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_fund_benchmark(self, fund_code: str) -> dict[str, Any]:
    """
    获取基金业绩比较基准.

    Args:
        fund_code: 基金代码

    Returns:
        业绩比较基准字典
    """
    # 获取基金基本信息
    info = self.get_fund_info(fund_code)

    return {
        "fund_code": fund_code,
        "benchmark": info.get("benchmark", "未知"),
    }

search_funds

search_funds(
    fund_type: str | None = None,
    company: str | None = None,
    min_scale: float | None = None,
    max_scale: float | None = None,
    keyword: str | None = None,
    limit: int = 100,
) -> pd.DataFrame

搜索基金.

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def search_funds(
    self,
    fund_type: str | None = None,
    company: str | None = None,
    min_scale: float | None = None,
    max_scale: float | None = None,
    keyword: str | None = None,
    limit: int = 100,
) -> pd.DataFrame:
    """搜索基金."""
    ts = self._get_tushare()
    self._rate_limit()

    try:
        df = ts.fund_basic(market="O")

        if fund_type:
            df = df[df["fund_type"].str.contains(fund_type, na=False)]
        if company:
            df = df[df["management"].str.contains(company, na=False)]
        if keyword:
            mask = df["ts_code"].str.contains(keyword, na=False) | df["name"].str.contains(keyword, na=False)
            df = df[mask]

        df = df.head(limit)

        df = df.rename(
            columns={
                "ts_code": "code",
                "name": "name",
                "fund_type": "type",
                "management": "company",
            }
        )

        return df.reset_index(drop=True)

    except Exception as e:
        raise DataSourceError(f"搜索基金失败: {e}") from e

get_fund_list

get_fund_list(fund_type: str | None = None) -> pd.DataFrame

获取基金列表.

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_fund_list(self, fund_type: str | None = None) -> pd.DataFrame:
    """获取基金列表."""
    return self.search_funds(fund_type=fund_type)

get_benchmark_nav

get_benchmark_nav(
    benchmark_code: str,
    start_date: date | None = None,
    end_date: date | None = None,
) -> pd.DataFrame

获取基准指数数据.

源代码位于: src/fund_cli/data/adapters/tushare_adapter.py
def get_benchmark_nav(
    self,
    benchmark_code: str,
    start_date: date | None = None,
    end_date: date | None = None,
) -> pd.DataFrame:
    """获取基准指数数据."""
    ts = self._get_tushare()
    self._rate_limit()

    try:
        # 转换指数代码格式
        if benchmark_code.startswith("0") or benchmark_code.startswith("1"):
            ts_code = f"{benchmark_code}.SH"
        else:
            ts_code = f"{benchmark_code}.SZ"

        df = ts.index_daily(
            ts_code=ts_code,
            start_date=start_date.strftime("%Y%m%d") if start_date else None,
            end_date=end_date.strftime("%Y%m%d") if end_date else None,
        )

        if df.empty:
            raise DataNotFoundError(f"指数 {benchmark_code} 不存在")

        df = self._convert_date_format(df, ["trade_date"])

        df = df.rename(
            columns={
                "trade_date": "nav_date",
                "close": "unit_nav",
            }
        )

        df["fund_code"] = benchmark_code
        df["daily_return"] = df["unit_nav"].pct_change() * 100
        df["accumulated_nav"] = df["unit_nav"]

        result_df = df[["fund_code", "nav_date", "unit_nav", "accumulated_nav", "daily_return"]].copy()
        return result_df.sort_values("nav_date").reset_index(drop=True)

    except DataNotFoundError:
        raise
    except Exception as e:
        raise DataSourceError(f"获取基准数据失败: {e}") from e

WindAdapter

Bases: DataSourceAdapterMixin, DataSourceAdapter


              flowchart TD
              fund_cli.data.adapters.wind_adapter.WindAdapter[WindAdapter]
              fund_cli.data.adapters.mixins.DataSourceAdapterMixin[DataSourceAdapterMixin]
              fund_cli.data.base.DataSourceAdapter[DataSourceAdapter]

                              fund_cli.data.adapters.mixins.DataSourceAdapterMixin --> fund_cli.data.adapters.wind_adapter.WindAdapter
                
                fund_cli.data.base.DataSourceAdapter --> fund_cli.data.adapters.wind_adapter.WindAdapter
                


              click fund_cli.data.adapters.wind_adapter.WindAdapter href "" "fund_cli.data.adapters.wind_adapter.WindAdapter"
              click fund_cli.data.adapters.mixins.DataSourceAdapterMixin href "" "fund_cli.data.adapters.mixins.DataSourceAdapterMixin"
              click fund_cli.data.base.DataSourceAdapter href "" "fund_cli.data.base.DataSourceAdapter"
            

Wind 数据源适配器(占位实现).

Wind(万得)是中国领先的金融数据提供商,特点: - 数据覆盖最全面 - 需要商业授权 - 通过 WindPy Python 接口访问

当前为占位实现,待 WindPy 授权后完善。

源代码位于: src/fund_cli/data/adapters/wind_adapter.py
class WindAdapter(DataSourceAdapterMixin, DataSourceAdapter):
    """
    Wind 数据源适配器(占位实现).

    Wind(万得)是中国领先的金融数据提供商,特点:
    - 数据覆盖最全面
    - 需要商业授权
    - 通过 WindPy Python 接口访问

    当前为占位实现,待 WindPy 授权后完善。
    """

    def __init__(self, cache=None):
        super().__init__("wind")
        self._cache = cache
        self._api = None

    def is_available(self) -> bool:
        """检查 Wind 是否可用."""
        try:
            from WindPy import w
            return w.isconnected()
        except ImportError:
            return False
        except Exception:
            return False

    def _ensure_api(self):
        """确保 Wind API 已连接."""
        if self._api is None:
            try:
                from WindPy import w
                w.start()
                self._api = w
            except ImportError as exc:
                raise DataSourceError("WindPy 未安装。请安装 WindPy 并确保已授权。") from exc
            except Exception as e:
                raise DataSourceError(f"Wind 连接失败: {e}") from e

    # P0 核心方法 - 覆盖 Mixin 的占位实现
    def get_fund_info(self, fund_code: str) -> dict:
        self._ensure_api()
        raise DataSourceError("WindAdapter P0 方法待实现(需要 WindPy 授权)")

    def get_all_fund_names(self):
        self._ensure_api()
        raise DataSourceError("WindAdapter P0 方法待实现(需要 WindPy 授权)")

    def get_fund_nav(self, fund_code: str, start_date=None, end_date=None):
        self._ensure_api()
        raise DataSourceError("WindAdapter P0 方法待实现(需要 WindPy 授权)")

    def get_etf_spot(self):
        self._ensure_api()
        raise DataSourceError("WindAdapter P0 方法待实现(需要 WindPy 授权)")

    def get_lof_spot(self):
        self._ensure_api()
        raise DataSourceError("WindAdapter P0 方法待实现(需要 WindPy 授权)")

    def get_fund_manager(self, fund_code: str):
        self._ensure_api()
        raise DataSourceError("WindAdapter P0 方法待实现(需要 WindPy 授权)")

    def get_fund_holdings(self, fund_code: str, report_date=None):
        self._ensure_api()
        raise DataSourceError("WindAdapter P0 方法待实现(需要 WindPy 授权)")

    def get_fund_asset_allocation(self, fund_code: str, date=None):
        self._ensure_api()
        raise DataSourceError("WindAdapter P0 方法待实现(需要 WindPy 授权)")

    def get_fund_rating(self, fund_code: str):
        self._ensure_api()
        raise DataSourceError("WindAdapter P0 方法待实现(需要 WindPy 授权)")

    def get_fund_fee(self, fund_code: str):
        self._ensure_api()
        raise DataSourceError("WindAdapter P0 方法待实现(需要 WindPy 授权)")

    def batch_get_fund_nav(self, fund_codes, start_date=None, end_date=None):
        self._ensure_api()
        raise DataSourceError("WindAdapter P0 方法待实现(需要 WindPy 授权)")

is_available

is_available() -> bool

检查 Wind 是否可用.

源代码位于: src/fund_cli/data/adapters/wind_adapter.py
def is_available(self) -> bool:
    """检查 Wind 是否可用."""
    try:
        from WindPy import w
        return w.isconnected()
    except ImportError:
        return False
    except Exception:
        return False

DataNormalizer

数据标准化器.

功能: - 统一字段命名(snake_case) - 统一日期格式(YYYY-MM-DD) - 统一数据类型 - 缺失值处理

源代码位于: src/fund_cli/data/normalizer.py
class DataNormalizer:
    """
    数据标准化器.

    功能:
    - 统一字段命名(snake_case)
    - 统一日期格式(YYYY-MM-DD)
    - 统一数据类型
    - 缺失值处理
    """

    # 字段映射规则
    FIELD_MAPPINGS = {
        # 基金代码
        "ts_code": "fund_code",
        "symbol": "fund_code",
        "code": "fund_code",
        # 基金名称
        "name": "fund_name",
        "fund_name": "fund_name",
        # 净值相关
        "end_date": "nav_date",
        "trade_date": "nav_date",
        "unit_nav": "unit_nav",
        "accum_nav": "accumulated_nav",
        # 持仓相关
        "stock_code": "stock_code",
        "stock_name": "stock_name",
        "vol": "volume",
        "volume": "volume",
        "proportions": "proportion",
        "proportion": "proportion",
    }

    # 日期字段
    DATE_FIELDS = [
        "nav_date",
        "start_date",
        "end_date",
        "found_date",
        "list_date",
        "establish_date",
    ]

    @classmethod
    def normalize_fund_info(cls, data: dict[str, Any]) -> dict[str, Any]:
        """
        标准化基金信息.

        Args:
            data: 原始基金信息

        Returns:
            标准化后的基金信息
        """
        result = {}

        for key, value in data.items():
            # 字段映射
            normalized_key = cls.FIELD_MAPPINGS.get(key, key)
            result[normalized_key] = value

        # 标准化日期格式
        for date_field in cls.DATE_FIELDS:
            if date_field in result and result[date_field]:
                result[date_field] = cls.normalize_date(result[date_field])

        # 标准化基金代码格式
        if "fund_code" in result and result["fund_code"]:
            result["fund_code"] = cls.normalize_fund_code(result["fund_code"])

        return result

    @classmethod
    def normalize_nav_data(cls, df: pd.DataFrame) -> pd.DataFrame:
        """
        标准化净值数据.

        Args:
            df: 原始净值数据DataFrame

        Returns:
            标准化后的DataFrame
        """
        result = df.copy()

        # 重命名列
        rename_columns = {}
        for col in result.columns:
            if col in cls.FIELD_MAPPINGS:
                rename_columns[col] = cls.FIELD_MAPPINGS[col]
        result = result.rename(columns=rename_columns)

        # 确保必要列存在
        required_cols = ["fund_code", "nav_date", "unit_nav"]
        for col in required_cols:
            if col not in result.columns:
                raise ValueError(f"缺少必要列: {col}")

        # 标准化日期格式
        if "nav_date" in result.columns:
            result["nav_date"] = pd.to_datetime(result["nav_date"], errors="coerce").dt.strftime("%Y-%m-%d")

        # 标准化基金代码
        if "fund_code" in result.columns:
            result["fund_code"] = result["fund_code"].apply(cls.normalize_fund_code)

        # 确保数值类型
        numeric_cols = ["unit_nav", "accumulated_nav", "daily_return", "volume", "proportion"]
        for col in numeric_cols:
            if col in result.columns:
                result[col] = pd.to_numeric(result[col], errors="coerce")

        # 按日期排序
        result = result.sort_values("nav_date").reset_index(drop=True)

        return result

    @classmethod
    def normalize_fund_code(cls, code: str) -> str:
        """
        标准化基金代码.

        移除后缀(如.OF、.SH等),只保留纯数字代码

        Args:
            code: 原始基金代码

        Returns:
            标准化后的基金代码
        """
        if not code:
            return code

        # 移除常见后缀
        for suffix in [".OF", ".SH", ".SZ", ".BJ"]:
            code = code.replace(suffix, "")

        return code

    @staticmethod
    @lru_cache(maxsize=1024)
    def normalize_fund_code_cached(code: str) -> str:
        """带缓存的基金代码标准化."""
        return DataNormalizer.normalize_fund_code(code)

    @staticmethod
    @lru_cache(maxsize=1024)
    def normalize_date_cached(date_value: Any) -> str | None:
        """带缓存的日期标准化."""
        return DataNormalizer.normalize_date(date_value)

    @classmethod
    def normalize_date(cls, date_value: Any) -> str | None:
        """
        标准化日期格式.

        Args:
            date_value: 原始日期值

        Returns:
            YYYY-MM-DD格式的日期字符串
        """
        if not date_value:
            return None

        if isinstance(date_value, date):
            return date_value.strftime("%Y-%m-%d")

        if isinstance(date_value, datetime):
            return date_value.strftime("%Y-%m-%d")

        if isinstance(date_value, str):
            # 尝试多种格式
            formats = [
                "%Y%m%d",      # 20240101
                "%Y-%m-%d",    # 2024-01-01
                "%Y/%m/%d",    # 2024/01/01
                "%Y.%m.%d",    # 2024.01.01
            ]

            for fmt in formats:
                try:
                    dt = datetime.strptime(date_value, fmt)
                    return dt.strftime("%Y-%m-%d")
                except ValueError:
                    continue

        return str(date_value)

    @classmethod
    def normalize_fund_holdings(cls, df: pd.DataFrame) -> pd.DataFrame:
        """
        标准化基金持仓数据.

        Args:
            df: 原始持仓数据

        Returns:
            标准化后的DataFrame
        """
        result = df.copy()

        # 重命名列
        rename_columns = {}
        for col in result.columns:
            if col in cls.FIELD_MAPPINGS:
                rename_columns[col] = cls.FIELD_MAPPINGS[col]
        result = result.rename(columns=rename_columns)

        # 确保必要列存在
        required_cols = ["fund_code", "stock_code", "stock_name"]
        for col in required_cols:
            if col not in result.columns:
                raise ValueError(f"缺少必要列: {col}")

        # 标准化基金代码
        if "fund_code" in result.columns:
            result["fund_code"] = result["fund_code"].apply(cls.normalize_fund_code)

        # 确保数值类型
        if "volume" in result.columns:
            result["volume"] = pd.to_numeric(result["volume"], errors="coerce")
        if "proportion" in result.columns:
            result["proportion"] = pd.to_numeric(result["proportion"], errors="coerce")

        return result

    @classmethod
    def normalize_fund_manager(cls, df: pd.DataFrame) -> pd.DataFrame:
        """
        标准化基金经理数据.

        Args:
            df: 原始基金经理数据

        Returns:
            标准化后的DataFrame
        """
        result = df.copy()

        # 标准化日期
        for date_field in ["start_date", "end_date"]:
            if date_field in result.columns:
                result[date_field] = result[date_field].apply(cls.normalize_date)

        return result

    @classmethod
    def normalize_asset_allocation(cls, data: dict[str, Any]) -> dict[str, Any]:
        """
        标准化资产配置数据.

        Args:
            data: 原始资产配置数据

        Returns:
            标准化后的字典
        """
        stock_ratio: float = float(data.get("stock_ratio", 0) or 0)
        bond_ratio: float = float(data.get("bond_ratio", 0) or 0)
        cash_ratio: float = float(data.get("cash_ratio", 0) or 0)
        total_asset: float = float(data.get("total_asset", 0) or 0)

        result = {
            "fund_code": cls.normalize_fund_code(data.get("fund_code", "")),
            "date": cls.normalize_date(data.get("date", "")),
            "stock_ratio": stock_ratio,
            "bond_ratio": bond_ratio,
            "cash_ratio": cash_ratio,
            "total_asset": total_asset,
        }

        # 验证比例总和(应该接近100%)
        total_ratio = stock_ratio + bond_ratio + cash_ratio
        if total_ratio > 0:
            # 归一化
            result["stock_ratio"] = round(stock_ratio / total_ratio * 100, 2)
            result["bond_ratio"] = round(bond_ratio / total_ratio * 100, 2)
            result["cash_ratio"] = round(cash_ratio / total_ratio * 100, 2)

        return result

normalize_fund_info classmethod

normalize_fund_info(data: dict[str, Any]) -> dict[str, Any]

标准化基金信息.

参数:

名称 类型 描述 默认
data dict[str, Any]

原始基金信息

必需

返回:

类型 描述
dict[str, Any]

标准化后的基金信息

源代码位于: src/fund_cli/data/normalizer.py
@classmethod
def normalize_fund_info(cls, data: dict[str, Any]) -> dict[str, Any]:
    """
    标准化基金信息.

    Args:
        data: 原始基金信息

    Returns:
        标准化后的基金信息
    """
    result = {}

    for key, value in data.items():
        # 字段映射
        normalized_key = cls.FIELD_MAPPINGS.get(key, key)
        result[normalized_key] = value

    # 标准化日期格式
    for date_field in cls.DATE_FIELDS:
        if date_field in result and result[date_field]:
            result[date_field] = cls.normalize_date(result[date_field])

    # 标准化基金代码格式
    if "fund_code" in result and result["fund_code"]:
        result["fund_code"] = cls.normalize_fund_code(result["fund_code"])

    return result

normalize_nav_data classmethod

normalize_nav_data(df: DataFrame) -> pd.DataFrame

标准化净值数据.

参数:

名称 类型 描述 默认
df DataFrame

原始净值数据DataFrame

必需

返回:

类型 描述
DataFrame

标准化后的DataFrame

源代码位于: src/fund_cli/data/normalizer.py
@classmethod
def normalize_nav_data(cls, df: pd.DataFrame) -> pd.DataFrame:
    """
    标准化净值数据.

    Args:
        df: 原始净值数据DataFrame

    Returns:
        标准化后的DataFrame
    """
    result = df.copy()

    # 重命名列
    rename_columns = {}
    for col in result.columns:
        if col in cls.FIELD_MAPPINGS:
            rename_columns[col] = cls.FIELD_MAPPINGS[col]
    result = result.rename(columns=rename_columns)

    # 确保必要列存在
    required_cols = ["fund_code", "nav_date", "unit_nav"]
    for col in required_cols:
        if col not in result.columns:
            raise ValueError(f"缺少必要列: {col}")

    # 标准化日期格式
    if "nav_date" in result.columns:
        result["nav_date"] = pd.to_datetime(result["nav_date"], errors="coerce").dt.strftime("%Y-%m-%d")

    # 标准化基金代码
    if "fund_code" in result.columns:
        result["fund_code"] = result["fund_code"].apply(cls.normalize_fund_code)

    # 确保数值类型
    numeric_cols = ["unit_nav", "accumulated_nav", "daily_return", "volume", "proportion"]
    for col in numeric_cols:
        if col in result.columns:
            result[col] = pd.to_numeric(result[col], errors="coerce")

    # 按日期排序
    result = result.sort_values("nav_date").reset_index(drop=True)

    return result

normalize_fund_code classmethod

normalize_fund_code(code: str) -> str

标准化基金代码.

移除后缀(如.OF、.SH等),只保留纯数字代码

参数:

名称 类型 描述 默认
code str

原始基金代码

必需

返回:

类型 描述
str

标准化后的基金代码

源代码位于: src/fund_cli/data/normalizer.py
@classmethod
def normalize_fund_code(cls, code: str) -> str:
    """
    标准化基金代码.

    移除后缀(如.OF、.SH等),只保留纯数字代码

    Args:
        code: 原始基金代码

    Returns:
        标准化后的基金代码
    """
    if not code:
        return code

    # 移除常见后缀
    for suffix in [".OF", ".SH", ".SZ", ".BJ"]:
        code = code.replace(suffix, "")

    return code

normalize_fund_code_cached cached staticmethod

normalize_fund_code_cached(code: str) -> str

带缓存的基金代码标准化.

源代码位于: src/fund_cli/data/normalizer.py
@staticmethod
@lru_cache(maxsize=1024)
def normalize_fund_code_cached(code: str) -> str:
    """带缓存的基金代码标准化."""
    return DataNormalizer.normalize_fund_code(code)

normalize_date_cached cached staticmethod

normalize_date_cached(date_value: Any) -> str | None

带缓存的日期标准化.

源代码位于: src/fund_cli/data/normalizer.py
@staticmethod
@lru_cache(maxsize=1024)
def normalize_date_cached(date_value: Any) -> str | None:
    """带缓存的日期标准化."""
    return DataNormalizer.normalize_date(date_value)

normalize_date classmethod

normalize_date(date_value: Any) -> str | None

标准化日期格式.

参数:

名称 类型 描述 默认
date_value Any

原始日期值

必需

返回:

类型 描述
str | None

YYYY-MM-DD格式的日期字符串

源代码位于: src/fund_cli/data/normalizer.py
@classmethod
def normalize_date(cls, date_value: Any) -> str | None:
    """
    标准化日期格式.

    Args:
        date_value: 原始日期值

    Returns:
        YYYY-MM-DD格式的日期字符串
    """
    if not date_value:
        return None

    if isinstance(date_value, date):
        return date_value.strftime("%Y-%m-%d")

    if isinstance(date_value, datetime):
        return date_value.strftime("%Y-%m-%d")

    if isinstance(date_value, str):
        # 尝试多种格式
        formats = [
            "%Y%m%d",      # 20240101
            "%Y-%m-%d",    # 2024-01-01
            "%Y/%m/%d",    # 2024/01/01
            "%Y.%m.%d",    # 2024.01.01
        ]

        for fmt in formats:
            try:
                dt = datetime.strptime(date_value, fmt)
                return dt.strftime("%Y-%m-%d")
            except ValueError:
                continue

    return str(date_value)

normalize_fund_holdings classmethod

normalize_fund_holdings(df: DataFrame) -> pd.DataFrame

标准化基金持仓数据.

参数:

名称 类型 描述 默认
df DataFrame

原始持仓数据

必需

返回:

类型 描述
DataFrame

标准化后的DataFrame

源代码位于: src/fund_cli/data/normalizer.py
@classmethod
def normalize_fund_holdings(cls, df: pd.DataFrame) -> pd.DataFrame:
    """
    标准化基金持仓数据.

    Args:
        df: 原始持仓数据

    Returns:
        标准化后的DataFrame
    """
    result = df.copy()

    # 重命名列
    rename_columns = {}
    for col in result.columns:
        if col in cls.FIELD_MAPPINGS:
            rename_columns[col] = cls.FIELD_MAPPINGS[col]
    result = result.rename(columns=rename_columns)

    # 确保必要列存在
    required_cols = ["fund_code", "stock_code", "stock_name"]
    for col in required_cols:
        if col not in result.columns:
            raise ValueError(f"缺少必要列: {col}")

    # 标准化基金代码
    if "fund_code" in result.columns:
        result["fund_code"] = result["fund_code"].apply(cls.normalize_fund_code)

    # 确保数值类型
    if "volume" in result.columns:
        result["volume"] = pd.to_numeric(result["volume"], errors="coerce")
    if "proportion" in result.columns:
        result["proportion"] = pd.to_numeric(result["proportion"], errors="coerce")

    return result

normalize_fund_manager classmethod

normalize_fund_manager(df: DataFrame) -> pd.DataFrame

标准化基金经理数据.

参数:

名称 类型 描述 默认
df DataFrame

原始基金经理数据

必需

返回:

类型 描述
DataFrame

标准化后的DataFrame

源代码位于: src/fund_cli/data/normalizer.py
@classmethod
def normalize_fund_manager(cls, df: pd.DataFrame) -> pd.DataFrame:
    """
    标准化基金经理数据.

    Args:
        df: 原始基金经理数据

    Returns:
        标准化后的DataFrame
    """
    result = df.copy()

    # 标准化日期
    for date_field in ["start_date", "end_date"]:
        if date_field in result.columns:
            result[date_field] = result[date_field].apply(cls.normalize_date)

    return result

normalize_asset_allocation classmethod

normalize_asset_allocation(
    data: dict[str, Any],
) -> dict[str, Any]

标准化资产配置数据.

参数:

名称 类型 描述 默认
data dict[str, Any]

原始资产配置数据

必需

返回:

类型 描述
dict[str, Any]

标准化后的字典

源代码位于: src/fund_cli/data/normalizer.py
@classmethod
def normalize_asset_allocation(cls, data: dict[str, Any]) -> dict[str, Any]:
    """
    标准化资产配置数据.

    Args:
        data: 原始资产配置数据

    Returns:
        标准化后的字典
    """
    stock_ratio: float = float(data.get("stock_ratio", 0) or 0)
    bond_ratio: float = float(data.get("bond_ratio", 0) or 0)
    cash_ratio: float = float(data.get("cash_ratio", 0) or 0)
    total_asset: float = float(data.get("total_asset", 0) or 0)

    result = {
        "fund_code": cls.normalize_fund_code(data.get("fund_code", "")),
        "date": cls.normalize_date(data.get("date", "")),
        "stock_ratio": stock_ratio,
        "bond_ratio": bond_ratio,
        "cash_ratio": cash_ratio,
        "total_asset": total_asset,
    }

    # 验证比例总和(应该接近100%)
    total_ratio = stock_ratio + bond_ratio + cash_ratio
    if total_ratio > 0:
        # 归一化
        result["stock_ratio"] = round(stock_ratio / total_ratio * 100, 2)
        result["bond_ratio"] = round(bond_ratio / total_ratio * 100, 2)
        result["cash_ratio"] = round(cash_ratio / total_ratio * 100, 2)

    return result

FundInfo

Bases: BaseModel


              flowchart TD
              fund_cli.data.models.FundInfo[FundInfo]

              

              click fund_cli.data.models.FundInfo href "" "fund_cli.data.models.FundInfo"
            

基金基础信息模型

包含基金的基本属性信息。

源代码位于: src/fund_cli/data/models.py
class FundInfo(BaseModel):
    """
    基金基础信息模型

    包含基金的基本属性信息。
    """

    model_config = ConfigDict(
        str_strip_whitespace=True,
        validate_assignment=True,
    )

    # 基本信息
    code: str = Field(..., min_length=6, max_length=6, description="基金代码")
    name: str = Field(..., min_length=1, description="基金名称")
    type: FundType = Field(..., description="基金类型")

    # 详细信息
    establish_date: date | None = Field(None, description="成立日期")
    manager: str | None = Field(None, description="基金经理")
    company: str | None = Field(None, description="基金公司")
    scale: float | None = Field(None, ge=0, description="规模(亿元)")

    # 业绩信息
    return_1m: float | None = Field(None, description="近1月收益率(%)")
    return_3m: float | None = Field(None, description="近3月收益率(%)")
    return_6m: float | None = Field(None, description="近6月收益率(%)")
    return_1y: float | None = Field(None, description="近1年收益率(%)")
    return_3y: float | None = Field(None, description="近3年收益率(%)")
    return_this_year: float | None = Field(None, description="今年以来收益率(%)")

    # 风险指标
    max_drawdown: float | None = Field(None, description="最大回撤(%)")
    sharpe_ratio: float | None = Field(None, description="夏普比率")

    @field_validator("code")
    @classmethod
    def validate_code(cls, v: str) -> str:
        """验证基金代码格式"""
        if not v.isdigit():
            raise ValueError("基金代码必须为6位数字")
        return v

    def __repr__(self) -> str:
        return f"FundInfo(code={self.code!r}, name={self.name!r}, type={self.type.value!r})"

validate_code classmethod

validate_code(v: str) -> str

验证基金代码格式

源代码位于: src/fund_cli/data/models.py
@field_validator("code")
@classmethod
def validate_code(cls, v: str) -> str:
    """验证基金代码格式"""
    if not v.isdigit():
        raise ValueError("基金代码必须为6位数字")
    return v

Bases: BaseModel


              flowchart TD
              fund_cli.data.models.NavData[NavData]

              

              click fund_cli.data.models.NavData href "" "fund_cli.data.models.NavData"
            

单条净值数据模型

源代码位于: src/fund_cli/data/models.py
class NavData(BaseModel):
    """
    单条净值数据模型
    """

    model_config = ConfigDict(
        str_strip_whitespace=True,
    )

    fund_code: str = Field(..., description="基金代码")
    nav_date: date = Field(..., description="净值日期")
    unit_nav: float = Field(..., gt=0, description="单位净值")
    accumulated_nav: float | None = Field(None, gt=0, description="累计净值")
    daily_return: float | None = Field(None, description="日收益率(%)")

    @field_validator("fund_code")
    @classmethod
    def validate_fund_code(cls, v: str) -> str:
        """验证基金代码格式"""
        if not v.isdigit() or len(v) != 6:
            raise ValueError("基金代码必须为6位数字")
        return v

validate_fund_code classmethod

validate_fund_code(v: str) -> str

验证基金代码格式

源代码位于: src/fund_cli/data/models.py
@field_validator("fund_code")
@classmethod
def validate_fund_code(cls, v: str) -> str:
    """验证基金代码格式"""
    if not v.isdigit() or len(v) != 6:
        raise ValueError("基金代码必须为6位数字")
    return v