mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-10 14:27:00 +08:00
竞赛fix、交易员新增参数
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="/icons/nofx.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>NOFX - AI Auto Trading Dashboard</title>
|
||||
</head>
|
||||
|
||||
296
web/public/icons/nofx.svg
Normal file
296
web/public/icons/nofx.svg
Normal file
@@ -0,0 +1,296 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100%" viewBox="0 0 1024 1024" enable-background="new 0 0 1024 1024" xml:space="preserve">
|
||||
<path fill="#FDFDFD" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M500.000000,1025.000000
|
||||
C333.333344,1025.000000 167.166672,1025.000000 1.000000,1025.000000
|
||||
C1.000000,683.666687 1.000000,342.333344 1.000000,1.000000
|
||||
C342.333344,1.000000 683.666687,1.000000 1025.000000,1.000000
|
||||
C1025.000000,342.333344 1025.000000,683.666687 1025.000000,1025.000000
|
||||
C850.166687,1025.000000 675.333313,1025.000000 500.000000,1025.000000
|
||||
M534.031067,584.483826
|
||||
C534.031067,588.398560 534.031067,592.313354 534.031067,596.807495
|
||||
C545.055725,596.807495 555.191895,596.895569 565.324890,596.757568
|
||||
C568.675903,596.711914 570.108704,597.732422 570.067505,601.303345
|
||||
C569.908142,615.130981 569.858154,628.962708 570.028137,642.789673
|
||||
C570.077393,646.796997 568.523926,648.072266 564.742004,647.996033
|
||||
C556.247864,647.824707 547.744873,648.036621 539.253845,647.796204
|
||||
C535.514526,647.690369 533.817627,648.561218 533.996155,652.742493
|
||||
C534.308533,660.059753 533.817566,667.412292 534.157166,674.727051
|
||||
C534.375000,679.418884 533.066345,681.195984 528.085999,681.018494
|
||||
C517.601807,680.644897 507.094879,680.960999 496.599945,680.810852
|
||||
C493.243347,680.762817 491.914062,681.787109 491.941986,685.357300
|
||||
C492.094421,704.850281 492.122192,724.345581 491.971497,743.838440
|
||||
C491.941101,747.773010 493.228638,749.004578 497.124115,748.978394
|
||||
C518.949890,748.831421 540.777771,748.821838 562.603271,748.980225
|
||||
C566.173035,749.006165 567.215881,747.891907 567.153503,744.444214
|
||||
C566.966858,734.117310 567.222534,723.781128 566.953308,713.457703
|
||||
C566.847107,709.385132 568.577148,708.202698 572.203125,708.286438
|
||||
C578.188965,708.424683 584.192078,708.207458 590.160583,708.579163
|
||||
C594.476196,708.847839 596.032410,707.578918 595.856140,703.035217
|
||||
C595.503296,693.939453 595.751465,684.820312 595.751465,675.649231
|
||||
C596.925232,675.398560 597.552307,675.149048 598.180054,675.147583
|
||||
C615.339722,675.106445 632.499634,675.126099 649.658997,675.034668
|
||||
C652.399414,675.020081 653.028992,676.300964 653.009949,678.716370
|
||||
C652.946899,686.712646 653.113281,694.711670 652.973572,702.705811
|
||||
C652.917664,705.902710 653.994019,707.188904 657.276245,707.107605
|
||||
C663.269775,706.959106 669.278931,707.315979 675.266113,707.076050
|
||||
C679.086365,706.922974 680.126648,708.399414 680.068604,712.025635
|
||||
C679.897949,722.685486 680.140930,733.351624 679.989075,744.012024
|
||||
C679.937683,747.618469 681.041504,748.997803 684.821899,748.976501
|
||||
C708.314026,748.844055 731.807556,748.852051 755.299805,748.975403
|
||||
C758.820862,748.993835 759.868530,747.806763 759.844788,744.357910
|
||||
C759.709351,724.698547 759.709839,705.037598 759.818420,685.377991
|
||||
C759.837585,681.910950 758.672913,680.734314 755.189087,680.794067
|
||||
C744.195374,680.982727 733.194397,680.763184 722.201355,680.970520
|
||||
C718.237671,681.045227 716.403687,679.967041 716.487549,675.664490
|
||||
C716.636658,668.012939 716.185547,660.350403 716.298767,652.697144
|
||||
C716.354187,648.952393 714.940552,647.784058 711.308655,647.875549
|
||||
C703.149780,648.080994 694.974915,647.740051 686.821289,648.032654
|
||||
C682.197937,648.198547 680.731934,646.551575 680.854858,641.973938
|
||||
C681.136780,631.484619 680.943665,620.982361 680.937012,610.485352
|
||||
C680.928345,596.707458 680.925720,596.708313 694.772095,596.712830
|
||||
C701.716309,596.715027 708.660583,596.713257 716.156738,596.713257
|
||||
C716.156738,587.047791 716.274170,578.086670 716.100098,569.131104
|
||||
C716.028503,565.448486 717.396729,564.077209 721.057495,564.125061
|
||||
C731.718384,564.264526 742.382324,564.204712 753.044861,564.170166
|
||||
C755.140137,564.163330 757.234619,563.919800 759.374390,563.782654
|
||||
C759.374390,541.682739 759.374390,520.113953 759.374390,498.512848
|
||||
C758.314819,498.283966 757.679260,498.027313 757.043457,498.026611
|
||||
C732.552490,497.999329 708.061401,498.037842 683.570801,497.939880
|
||||
C680.143433,497.926178 679.874512,499.756226 679.904175,502.387390
|
||||
C680.015015,512.215210 679.874512,522.048523 680.153259,531.870544
|
||||
C680.266418,535.860596 679.224426,537.680786 674.914673,537.479248
|
||||
C669.263062,537.215027 663.581482,537.623352 657.925659,537.406860
|
||||
C654.311951,537.268616 652.858887,538.322510 652.991699,542.182861
|
||||
C653.266357,550.170227 652.971191,558.175842 653.164490,566.167786
|
||||
C653.254150,569.872559 651.856873,571.069702 648.200317,571.033142
|
||||
C632.374023,570.874939 616.544556,570.857117 600.718201,571.000671
|
||||
C597.009949,571.034241 595.702759,569.735596 595.771912,566.091858
|
||||
C595.923645,558.098816 595.724426,550.099670 595.818298,542.104797
|
||||
C595.854309,539.035889 594.871948,537.527527 591.554504,537.577759
|
||||
C585.402100,537.671082 579.222473,537.141418 573.097961,537.541626
|
||||
C567.969849,537.876709 566.857544,535.717102 566.994751,531.152100
|
||||
C567.259827,522.329468 567.088135,513.493835 567.107239,504.663605
|
||||
C567.121216,498.211700 567.128967,498.312378 560.831177,498.362671
|
||||
C539.515869,498.532898 518.197937,498.814789 496.885956,498.590637
|
||||
C492.527435,498.544800 491.869690,499.960571 491.895660,503.604095
|
||||
C492.027496,522.097412 492.121552,540.592773 491.953308,559.084961
|
||||
C491.917023,563.073425 493.143402,564.285767 497.052917,564.207214
|
||||
C507.545197,563.996338 518.045471,564.219666 528.540771,564.103271
|
||||
C532.401672,564.060364 534.411804,565.121643 534.089355,569.498840
|
||||
C533.747498,574.138367 534.024414,578.823547 534.031067,584.483826
|
||||
M253.353867,389.499359
|
||||
C253.353867,425.600555 253.353867,461.701721 253.353867,498.020203
|
||||
C273.984375,498.020203 294.234894,498.020203 314.879120,498.020203
|
||||
C314.879120,466.733032 314.879120,435.525452 314.879120,404.231567
|
||||
C325.188171,404.231567 335.102448,404.231567 345.353882,404.231567
|
||||
C345.353882,416.582062 345.353882,428.655182 345.353882,441.171600
|
||||
C356.752899,441.171600 367.816437,441.171600 379.144989,441.171600
|
||||
C379.144989,452.733887 379.144989,463.845337 379.144989,475.463928
|
||||
C387.484772,475.463928 395.556122,475.463928 404.001831,475.463928
|
||||
C404.001831,483.396393 404.001831,490.822571 404.001831,498.353760
|
||||
C424.266266,498.353760 444.136444,498.353760 464.049652,498.353760
|
||||
C464.049652,428.578674 464.049652,359.158661 464.049652,289.508362
|
||||
C443.934937,289.508362 424.057556,289.508362 403.805725,289.508362
|
||||
C403.805725,326.036926 403.805725,362.284485 403.805725,398.714264
|
||||
C393.694244,398.714264 383.958435,398.714264 373.754669,398.714264
|
||||
C373.754669,387.079407 373.754669,375.679657 373.754669,363.763580
|
||||
C364.226013,363.763580 355.157928,363.763580 345.754578,363.763580
|
||||
C345.754578,350.975952 345.754578,338.719330 345.754578,326.013428
|
||||
C335.334686,326.013428 325.264954,326.013428 314.727661,326.013428
|
||||
C314.727661,313.817993 314.727661,302.078217 314.727661,290.294006
|
||||
C294.024200,290.294006 273.800537,290.294006 253.353867,290.294006
|
||||
C253.353867,323.258392 253.353867,355.878815 253.353867,389.499359
|
||||
M253.324738,540.524414
|
||||
C253.324738,609.787170 253.324738,679.049988 253.324738,748.416748
|
||||
C273.913696,748.416748 293.998413,748.416748 314.709351,748.416748
|
||||
C314.709351,719.517822 314.709351,690.832581 314.709351,661.901611
|
||||
C350.772034,661.901611 386.208771,661.901611 421.853760,661.901611
|
||||
C421.853760,645.744873 421.853760,629.843628 421.853760,613.658813
|
||||
C386.514313,613.658813 351.406067,613.658813 315.981873,613.658813
|
||||
C315.981873,602.016785 315.981873,590.659973 315.981873,578.921387
|
||||
C365.640533,578.921387 414.883820,578.921387 464.020874,578.921387
|
||||
C464.020874,562.126465 464.020874,545.717834 464.020874,529.276367
|
||||
C393.615936,529.276367 323.565338,529.276367 253.319580,529.276367
|
||||
C253.319580,532.918762 253.319580,536.226440 253.324738,540.524414
|
||||
M492.003387,396.500000
|
||||
C492.003387,418.075806 492.003387,439.651642 492.003387,461.703796
|
||||
C504.388947,461.703796 516.107178,461.703796 528.313721,461.703796
|
||||
C528.313721,472.416870 528.313721,482.654999 528.313721,492.669708
|
||||
C565.543945,492.669708 602.271057,492.669708 639.460571,492.669708
|
||||
C639.460571,481.874329 639.460571,471.464661 639.460571,460.745056
|
||||
C652.522522,460.745056 665.122131,460.745056 677.734863,460.745056
|
||||
C677.734863,427.692261 677.734863,394.973724 677.734863,361.929138
|
||||
C664.783020,361.929138 652.186462,361.929138 639.150879,361.929138
|
||||
C639.150879,350.688141 639.150879,339.765076 639.150879,328.555176
|
||||
C601.986938,328.555176 565.268433,328.555176 527.973755,328.555176
|
||||
C527.973755,339.860962 527.973755,350.904510 527.973755,362.241943
|
||||
C515.652466,362.241943 503.905975,362.241943 492.002960,362.241943
|
||||
C492.002960,373.544617 492.002960,384.522308 492.003387,396.500000
|
||||
M680.952881,342.679718
|
||||
C699.195862,342.679718 717.438782,342.679718 735.967041,342.679718
|
||||
C735.967041,325.691925 736.031189,309.222900 735.806641,292.757812
|
||||
C735.793213,291.775330 733.476685,289.998322 732.215942,289.983917
|
||||
C715.887634,289.797485 699.555664,289.801270 683.227722,290.003845
|
||||
C682.113281,290.017670 680.069885,291.995911 680.064697,293.068268
|
||||
C679.986694,309.383881 680.176758,325.700775 680.952881,342.679718
|
||||
z"/>
|
||||
<path fill="#F9AF07" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M534.031128,583.986328
|
||||
C534.024414,578.823547 533.747498,574.138367 534.089355,569.498840
|
||||
C534.411804,565.121643 532.401672,564.060364 528.540771,564.103271
|
||||
C518.045471,564.219666 507.545197,563.996338 497.052917,564.207214
|
||||
C493.143402,564.285767 491.917023,563.073425 491.953308,559.084961
|
||||
C492.121552,540.592773 492.027496,522.097412 491.895660,503.604095
|
||||
C491.869690,499.960571 492.527435,498.544800 496.885956,498.590637
|
||||
C518.197937,498.814789 539.515869,498.532898 560.831177,498.362671
|
||||
C567.128967,498.312378 567.121216,498.211700 567.107239,504.663605
|
||||
C567.088135,513.493835 567.259827,522.329468 566.994751,531.152100
|
||||
C566.857544,535.717102 567.969849,537.876709 573.097961,537.541626
|
||||
C579.222473,537.141418 585.402100,537.671082 591.554504,537.577759
|
||||
C594.871948,537.527527 595.854309,539.035889 595.818298,542.104797
|
||||
C595.724426,550.099670 595.923645,558.098816 595.771912,566.091858
|
||||
C595.702759,569.735596 597.009949,571.034241 600.718201,571.000671
|
||||
C616.544556,570.857117 632.374023,570.874939 648.200317,571.033142
|
||||
C651.856873,571.069702 653.254150,569.872559 653.164490,566.167786
|
||||
C652.971191,558.175842 653.266357,550.170227 652.991699,542.182861
|
||||
C652.858887,538.322510 654.311951,537.268616 657.925659,537.406860
|
||||
C663.581482,537.623352 669.263062,537.215027 674.914673,537.479248
|
||||
C679.224426,537.680786 680.266418,535.860596 680.153259,531.870544
|
||||
C679.874512,522.048523 680.015015,512.215210 679.904175,502.387390
|
||||
C679.874512,499.756226 680.143433,497.926178 683.570801,497.939880
|
||||
C708.061401,498.037842 732.552490,497.999329 757.043457,498.026611
|
||||
C757.679260,498.027313 758.314819,498.283966 759.374390,498.512848
|
||||
C759.374390,520.113953 759.374390,541.682739 759.374390,563.782654
|
||||
C757.234619,563.919800 755.140137,564.163330 753.044861,564.170166
|
||||
C742.382324,564.204712 731.718384,564.264526 721.057495,564.125061
|
||||
C717.396729,564.077209 716.028503,565.448486 716.100098,569.131104
|
||||
C716.274170,578.086670 716.156738,587.047791 716.156738,596.713257
|
||||
C708.660583,596.713257 701.716309,596.715027 694.772095,596.712830
|
||||
C680.925720,596.708313 680.928345,596.707458 680.937012,610.485352
|
||||
C680.943665,620.982361 681.136780,631.484619 680.854858,641.973938
|
||||
C680.731934,646.551575 682.197937,648.198547 686.821289,648.032654
|
||||
C694.974915,647.740051 703.149780,648.080994 711.308655,647.875549
|
||||
C714.940552,647.784058 716.354187,648.952393 716.298767,652.697144
|
||||
C716.185547,660.350403 716.636658,668.012939 716.487549,675.664490
|
||||
C716.403687,679.967041 718.237671,681.045227 722.201355,680.970520
|
||||
C733.194397,680.763184 744.195374,680.982727 755.189087,680.794067
|
||||
C758.672913,680.734314 759.837585,681.910950 759.818420,685.377991
|
||||
C759.709839,705.037598 759.709351,724.698547 759.844788,744.357910
|
||||
C759.868530,747.806763 758.820862,748.993835 755.299805,748.975403
|
||||
C731.807556,748.852051 708.314026,748.844055 684.821899,748.976501
|
||||
C681.041504,748.997803 679.937683,747.618469 679.989075,744.012024
|
||||
C680.140930,733.351624 679.897949,722.685486 680.068604,712.025635
|
||||
C680.126648,708.399414 679.086365,706.922974 675.266113,707.076050
|
||||
C669.278931,707.315979 663.269775,706.959106 657.276245,707.107605
|
||||
C653.994019,707.188904 652.917664,705.902710 652.973572,702.705811
|
||||
C653.113281,694.711670 652.946899,686.712646 653.009949,678.716370
|
||||
C653.028992,676.300964 652.399414,675.020081 649.658997,675.034668
|
||||
C632.499634,675.126099 615.339722,675.106445 598.180054,675.147583
|
||||
C597.552307,675.149048 596.925232,675.398560 595.751465,675.649231
|
||||
C595.751465,684.820312 595.503296,693.939453 595.856140,703.035217
|
||||
C596.032410,707.578918 594.476196,708.847839 590.160583,708.579163
|
||||
C584.192078,708.207458 578.188965,708.424683 572.203125,708.286438
|
||||
C568.577148,708.202698 566.847107,709.385132 566.953308,713.457703
|
||||
C567.222534,723.781128 566.966858,734.117310 567.153503,744.444214
|
||||
C567.215881,747.891907 566.173035,749.006165 562.603271,748.980225
|
||||
C540.777771,748.821838 518.949890,748.831421 497.124115,748.978394
|
||||
C493.228638,749.004578 491.941101,747.773010 491.971497,743.838440
|
||||
C492.122192,724.345581 492.094421,704.850281 491.941986,685.357300
|
||||
C491.914062,681.787109 493.243347,680.762817 496.599945,680.810852
|
||||
C507.094879,680.960999 517.601807,680.644897 528.085999,681.018494
|
||||
C533.066345,681.195984 534.375000,679.418884 534.157166,674.727051
|
||||
C533.817566,667.412292 534.308533,660.059753 533.996155,652.742493
|
||||
C533.817627,648.561218 535.514526,647.690369 539.253845,647.796204
|
||||
C547.744873,648.036621 556.247864,647.824707 564.742004,647.996033
|
||||
C568.523926,648.072266 570.077393,646.796997 570.028137,642.789673
|
||||
C569.858154,628.962708 569.908142,615.130981 570.067505,601.303345
|
||||
C570.108704,597.732422 568.675903,596.711914 565.324890,596.757568
|
||||
C555.191895,596.895569 545.055725,596.807495 534.031067,596.807495
|
||||
C534.031067,592.313354 534.031067,588.398560 534.031128,583.986328
|
||||
z"/>
|
||||
<path fill="#020202" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M253.353867,388.999268
|
||||
C253.353867,355.878815 253.353867,323.258392 253.353867,290.294006
|
||||
C273.800537,290.294006 294.024200,290.294006 314.727661,290.294006
|
||||
C314.727661,302.078217 314.727661,313.817993 314.727661,326.013428
|
||||
C325.264954,326.013428 335.334686,326.013428 345.754578,326.013428
|
||||
C345.754578,338.719330 345.754578,350.975952 345.754578,363.763580
|
||||
C355.157928,363.763580 364.226013,363.763580 373.754669,363.763580
|
||||
C373.754669,375.679657 373.754669,387.079407 373.754669,398.714264
|
||||
C383.958435,398.714264 393.694244,398.714264 403.805725,398.714264
|
||||
C403.805725,362.284485 403.805725,326.036926 403.805725,289.508362
|
||||
C424.057556,289.508362 443.934937,289.508362 464.049652,289.508362
|
||||
C464.049652,359.158661 464.049652,428.578674 464.049652,498.353760
|
||||
C444.136444,498.353760 424.266266,498.353760 404.001831,498.353760
|
||||
C404.001831,490.822571 404.001831,483.396393 404.001831,475.463928
|
||||
C395.556122,475.463928 387.484772,475.463928 379.144989,475.463928
|
||||
C379.144989,463.845337 379.144989,452.733887 379.144989,441.171600
|
||||
C367.816437,441.171600 356.752899,441.171600 345.353882,441.171600
|
||||
C345.353882,428.655182 345.353882,416.582062 345.353882,404.231567
|
||||
C335.102448,404.231567 325.188171,404.231567 314.879120,404.231567
|
||||
C314.879120,435.525452 314.879120,466.733032 314.879120,498.020203
|
||||
C294.234894,498.020203 273.984375,498.020203 253.353867,498.020203
|
||||
C253.353867,461.701721 253.353867,425.600555 253.353867,388.999268
|
||||
z"/>
|
||||
<path fill="#020202" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M253.322159,540.029297
|
||||
C253.319580,536.226440 253.319580,532.918762 253.319580,529.276367
|
||||
C323.565338,529.276367 393.615936,529.276367 464.020874,529.276367
|
||||
C464.020874,545.717834 464.020874,562.126465 464.020874,578.921387
|
||||
C414.883820,578.921387 365.640533,578.921387 315.981873,578.921387
|
||||
C315.981873,590.659973 315.981873,602.016785 315.981873,613.658813
|
||||
C351.406067,613.658813 386.514313,613.658813 421.853760,613.658813
|
||||
C421.853760,629.843628 421.853760,645.744873 421.853760,661.901611
|
||||
C386.208771,661.901611 350.772034,661.901611 314.709351,661.901611
|
||||
C314.709351,690.832581 314.709351,719.517822 314.709351,748.416748
|
||||
C293.998413,748.416748 273.913696,748.416748 253.324738,748.416748
|
||||
C253.324738,679.049988 253.324738,609.787170 253.322159,540.029297
|
||||
z"/>
|
||||
<path fill="#020202" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M492.003174,396.000000
|
||||
C492.002960,384.522308 492.002960,373.544617 492.002960,362.241943
|
||||
C503.905975,362.241943 515.652466,362.241943 527.973755,362.241943
|
||||
C527.973755,350.904510 527.973755,339.860962 527.973755,328.555176
|
||||
C565.268433,328.555176 601.986938,328.555176 639.150879,328.555176
|
||||
C639.150879,339.765076 639.150879,350.688141 639.150879,361.929138
|
||||
C652.186462,361.929138 664.783020,361.929138 677.734863,361.929138
|
||||
C677.734863,394.973724 677.734863,427.692261 677.734863,460.745056
|
||||
C665.122131,460.745056 652.522522,460.745056 639.460571,460.745056
|
||||
C639.460571,471.464661 639.460571,481.874329 639.460571,492.669708
|
||||
C602.271057,492.669708 565.543945,492.669708 528.313721,492.669708
|
||||
C528.313721,482.654999 528.313721,472.416870 528.313721,461.703796
|
||||
C516.107178,461.703796 504.388947,461.703796 492.003387,461.703796
|
||||
C492.003387,439.651642 492.003387,418.075806 492.003174,396.000000
|
||||
M614.295959,428.499878
|
||||
C614.295959,410.563873 614.295959,392.627899 614.295959,374.437195
|
||||
C594.356567,374.437195 574.817383,374.437195 555.216553,374.437195
|
||||
C555.216553,399.301392 555.216553,423.880280 555.216553,448.470581
|
||||
C574.879578,448.470581 594.303711,448.470581 614.296204,448.470581
|
||||
C614.296204,442.071533 614.296204,435.785706 614.295959,428.499878
|
||||
z"/>
|
||||
<path fill="#F7AF0C" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M680.628540,342.348541
|
||||
C680.176758,325.700775 679.986694,309.383881 680.064697,293.068268
|
||||
C680.069885,291.995911 682.113281,290.017670 683.227722,290.003845
|
||||
C699.555664,289.801270 715.887634,289.797485 732.215942,289.983917
|
||||
C733.476685,289.998322 735.793213,291.775330 735.806641,292.757812
|
||||
C736.031189,309.222900 735.967041,325.691925 735.967041,342.679718
|
||||
C717.438782,342.679718 699.195862,342.679718 680.628540,342.348541
|
||||
z"/>
|
||||
<path fill="#FCFCFC" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M614.296082,428.999878
|
||||
C614.296204,435.785706 614.296204,442.071533 614.296204,448.470581
|
||||
C594.303711,448.470581 574.879578,448.470581 555.216553,448.470581
|
||||
C555.216553,423.880280 555.216553,399.301392 555.216553,374.437195
|
||||
C574.817383,374.437195 594.356567,374.437195 614.295959,374.437195
|
||||
C614.295959,392.627899 614.295959,410.563873 614.296082,428.999878
|
||||
z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 18 KiB |
@@ -201,8 +201,8 @@ function App() {
|
||||
<div className="relative flex items-center">
|
||||
{/* Left - Logo and Title */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full flex items-center justify-center text-xl" style={{ background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)' }}>
|
||||
⚡
|
||||
<div className="w-8 h-8 flex items-center justify-center">
|
||||
<img src="/icons/nofx.svg?v=2" alt="NOFX" className="w-8 h-8" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-xl font-bold" style={{ color: '#EAECEF' }}>
|
||||
@@ -224,7 +224,7 @@ function App() {
|
||||
: { background: 'transparent', color: '#848E9C' }
|
||||
}
|
||||
>
|
||||
竞赛
|
||||
{t('aiCompetition', language)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentPage('traders')}
|
||||
@@ -264,7 +264,7 @@ function App() {
|
||||
{/* Admin Mode Indicator */}
|
||||
{systemConfig?.admin_mode && (
|
||||
<div className="flex items-center gap-2 px-3 py-2 rounded" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
||||
<span className="text-sm font-semibold" style={{ color: '#F0B90B' }}>⚡ 管理员模式</span>
|
||||
<span className="text-sm font-semibold" style={{ color: '#F0B90B' }}>⚡ {t('adminMode', language)}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -438,7 +438,7 @@ function TraderDetailsPage({
|
||||
{/* Trader Selector */}
|
||||
{traders && traders.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm" style={{ color: '#848E9C' }}>切换交易员:</span>
|
||||
<span className="text-sm" style={{ color: '#848E9C' }}>{t('switchTrader', language)}:</span>
|
||||
<select
|
||||
value={selectedTraderId}
|
||||
onChange={(e) => onTraderSelect(e.target.value)}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,12 +14,15 @@ import useSWR from 'swr';
|
||||
import { api } from '../lib/api';
|
||||
import type { CompetitionTraderData } from '../types';
|
||||
import { getTraderColor } from '../utils/traderColors';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import { t } from '../i18n/translations';
|
||||
|
||||
interface ComparisonChartProps {
|
||||
traders: CompetitionTraderData[];
|
||||
}
|
||||
|
||||
export function ComparisonChart({ traders }: ComparisonChartProps) {
|
||||
const { language } = useLanguage();
|
||||
// 获取所有trader的历史数据 - 使用单个useSWR并发请求所有trader数据
|
||||
// 生成唯一的key,当traders变化时会触发重新请求
|
||||
const tradersKey = traders.map(t => t.trader_id).sort().join(',');
|
||||
@@ -134,8 +137,8 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
|
||||
return (
|
||||
<div className="text-center py-16" style={{ color: '#848E9C' }}>
|
||||
<div className="text-6xl mb-4 opacity-50">📊</div>
|
||||
<div className="text-lg font-semibold mb-2">暂无历史数据</div>
|
||||
<div className="text-sm">运行几个周期后将显示对比曲线</div>
|
||||
<div className="text-lg font-semibold mb-2">{t('noHistoricalData', language)}</div>
|
||||
<div className="text-sm">{t('dataWillAppear', language)}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -311,25 +314,25 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
|
||||
{/* Stats */}
|
||||
<div className="mt-6 grid grid-cols-4 gap-4 pt-5" style={{ borderTop: '1px solid #2B3139' }}>
|
||||
<div className="p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>对比模式</div>
|
||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('comparisonMode', language)}</div>
|
||||
<div className="text-base font-bold" style={{ color: '#EAECEF' }}>PnL %</div>
|
||||
</div>
|
||||
<div className="p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>数据点数</div>
|
||||
<div className="text-base font-bold mono" style={{ color: '#EAECEF' }}>{combinedData.length} 个</div>
|
||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('dataPoints', language)}</div>
|
||||
<div className="text-base font-bold mono" style={{ color: '#EAECEF' }}>{t('count', language, {count: combinedData.length})}</div>
|
||||
</div>
|
||||
<div className="p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>当前差距</div>
|
||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('currentGap', language)}</div>
|
||||
<div className="text-base font-bold mono" style={{ color: currentGap > 1 ? '#F0B90B' : '#EAECEF' }}>
|
||||
{currentGap.toFixed(2)}%
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>显示范围</div>
|
||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('displayRange', language)}</div>
|
||||
<div className="text-base font-bold mono" style={{ color: '#EAECEF' }}>
|
||||
{combinedData.length > MAX_DISPLAY_POINTS
|
||||
? `最近 ${MAX_DISPLAY_POINTS}`
|
||||
: '全部数据'}
|
||||
? `${t('recent', language)} ${MAX_DISPLAY_POINTS}`
|
||||
: t('allData', language)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { useState } from 'react';
|
||||
import useSWR from 'swr';
|
||||
import { api } from '../lib/api';
|
||||
import type { CompetitionData } from '../types';
|
||||
import { ComparisonChart } from './ComparisonChart';
|
||||
import { TraderConfigModal } from './TraderConfigModal';
|
||||
import { getTraderColor } from '../utils/traderColors';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import { t } from '../i18n/translations';
|
||||
|
||||
export function CompetitionPage() {
|
||||
const { language } = useLanguage();
|
||||
const [selectedTrader, setSelectedTrader] = useState<any>(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
const { data: competition } = useSWR<CompetitionData>(
|
||||
'competition',
|
||||
api.getCompetition,
|
||||
@@ -15,6 +23,21 @@ export function CompetitionPage() {
|
||||
}
|
||||
);
|
||||
|
||||
const handleTraderClick = async (traderId: string) => {
|
||||
try {
|
||||
const traderConfig = await api.getTraderConfig(traderId);
|
||||
setSelectedTrader(traderConfig);
|
||||
setIsModalOpen(true);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch trader config:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setIsModalOpen(false);
|
||||
setSelectedTrader(null);
|
||||
};
|
||||
|
||||
if (!competition || !competition.traders) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -59,18 +82,18 @@ export function CompetitionPage() {
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||
AI竞赛
|
||||
{t('aiCompetition', language)}
|
||||
<span className="text-xs font-normal px-2 py-1 rounded" style={{ background: 'rgba(240, 185, 11, 0.15)', color: '#F0B90B' }}>
|
||||
{competition.count} 交易员
|
||||
{competition.count} {t('traders', language)}
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-xs" style={{ color: '#848E9C' }}>
|
||||
实时对战
|
||||
{t('liveBattle', language)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-xs mb-1" style={{ color: '#848E9C' }}>领先者</div>
|
||||
<div className="text-xs mb-1" style={{ color: '#848E9C' }}>{t('leader', language)}</div>
|
||||
<div className="text-lg font-bold" style={{ color: '#F0B90B' }}>{leader?.trader_name}</div>
|
||||
<div className="text-sm font-semibold" style={{ color: (leader?.total_pnl ?? 0) >= 0 ? '#0ECB81' : '#F6465D' }}>
|
||||
{(leader?.total_pnl ?? 0) >= 0 ? '+' : ''}{leader?.total_pnl_pct?.toFixed(2) || '0.00'}%
|
||||
@@ -84,10 +107,10 @@ export function CompetitionPage() {
|
||||
<div className="binance-card p-5 animate-slide-in" style={{ animationDelay: '0.1s' }}>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||
表现对比
|
||||
{t('performanceComparison', language)}
|
||||
</h2>
|
||||
<div className="text-xs" style={{ color: '#848E9C' }}>
|
||||
实时收益率
|
||||
{t('realTimePnL', language)}
|
||||
</div>
|
||||
</div>
|
||||
<ComparisonChart traders={sortedTraders} />
|
||||
@@ -97,10 +120,10 @@ export function CompetitionPage() {
|
||||
<div className="binance-card p-5 animate-slide-in" style={{ animationDelay: '0.1s' }}>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||
排行榜
|
||||
{t('leaderboard', language)}
|
||||
</h2>
|
||||
<div className="text-xs px-2 py-1 rounded" style={{ background: 'rgba(240, 185, 11, 0.1)', color: '#F0B90B', border: '1px solid rgba(240, 185, 11, 0.2)' }}>
|
||||
实时
|
||||
{t('live', language)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
@@ -111,7 +134,8 @@ export function CompetitionPage() {
|
||||
return (
|
||||
<div
|
||||
key={trader.trader_id}
|
||||
className="rounded p-3 transition-all duration-300 hover:translate-y-[-1px]"
|
||||
onClick={() => handleTraderClick(trader.trader_id)}
|
||||
className="rounded p-3 transition-all duration-300 hover:translate-y-[-1px] cursor-pointer hover:shadow-lg"
|
||||
style={{
|
||||
background: isLeader ? 'linear-gradient(135deg, rgba(240, 185, 11, 0.08) 0%, #0B0E11 100%)' : '#0B0E11',
|
||||
border: `1px solid ${isLeader ? 'rgba(240, 185, 11, 0.4)' : '#2B3139'}`,
|
||||
@@ -127,7 +151,7 @@ export function CompetitionPage() {
|
||||
<div>
|
||||
<div className="font-bold text-sm" style={{ color: '#EAECEF' }}>{trader.trader_name}</div>
|
||||
<div className="text-xs mono font-semibold" style={{ color: traderColor }}>
|
||||
{trader.ai_model.toUpperCase()}
|
||||
{trader.ai_model.toUpperCase()} + {trader.exchange.toUpperCase()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -136,7 +160,7 @@ export function CompetitionPage() {
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Total Equity */}
|
||||
<div className="text-right">
|
||||
<div className="text-xs" style={{ color: '#848E9C' }}>权益</div>
|
||||
<div className="text-xs" style={{ color: '#848E9C' }}>{t('equity', language)}</div>
|
||||
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
||||
{trader.total_equity?.toFixed(2) || '0.00'}
|
||||
</div>
|
||||
@@ -144,7 +168,7 @@ export function CompetitionPage() {
|
||||
|
||||
{/* P&L */}
|
||||
<div className="text-right min-w-[90px]">
|
||||
<div className="text-xs" style={{ color: '#848E9C' }}>收益</div>
|
||||
<div className="text-xs" style={{ color: '#848E9C' }}>{t('pnl', language)}</div>
|
||||
<div
|
||||
className="text-lg font-bold mono"
|
||||
style={{ color: (trader.total_pnl ?? 0) >= 0 ? '#0ECB81' : '#F6465D' }}
|
||||
@@ -159,7 +183,7 @@ export function CompetitionPage() {
|
||||
|
||||
{/* Positions */}
|
||||
<div className="text-right">
|
||||
<div className="text-xs" style={{ color: '#848E9C' }}>持仓</div>
|
||||
<div className="text-xs" style={{ color: '#848E9C' }}>{t('pos', language)}</div>
|
||||
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
||||
{trader.position_count}
|
||||
</div>
|
||||
@@ -193,7 +217,7 @@ export function CompetitionPage() {
|
||||
{competition.traders.length === 2 && (
|
||||
<div className="binance-card p-5 animate-slide-in" style={{ animationDelay: '0.3s' }}>
|
||||
<h2 className="text-lg font-bold mb-4 flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||
正面对决
|
||||
{t('headToHead', language)}
|
||||
</h2>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{sortedTraders.map((trader, index) => {
|
||||
@@ -220,22 +244,25 @@ export function CompetitionPage() {
|
||||
>
|
||||
<div className="text-center">
|
||||
<div
|
||||
className="text-base font-bold mb-2"
|
||||
className="text-base font-bold mb-1"
|
||||
style={{ color: getTraderColor(sortedTraders, trader.trader_id) }}
|
||||
>
|
||||
{trader.trader_name}
|
||||
</div>
|
||||
<div className="text-xs mono mb-2" style={{ color: '#848E9C' }}>
|
||||
{trader.ai_model.toUpperCase()} + {trader.exchange.toUpperCase()}
|
||||
</div>
|
||||
<div className="text-2xl font-bold mono mb-1" style={{ color: (trader.total_pnl ?? 0) >= 0 ? '#0ECB81' : '#F6465D' }}>
|
||||
{(trader.total_pnl ?? 0) >= 0 ? '+' : ''}{trader.total_pnl_pct?.toFixed(2) || '0.00'}%
|
||||
</div>
|
||||
{isWinning && gap > 0 && (
|
||||
<div className="text-xs font-semibold" style={{ color: '#0ECB81' }}>
|
||||
领先 {gap.toFixed(2)}%
|
||||
{t('leadingBy', language, { gap: gap.toFixed(2) })}
|
||||
</div>
|
||||
)}
|
||||
{!isWinning && gap < 0 && (
|
||||
<div className="text-xs font-semibold" style={{ color: '#F6465D' }}>
|
||||
落后 {Math.abs(gap).toFixed(2)}%
|
||||
{t('behindBy', language, { gap: Math.abs(gap).toFixed(2) })}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -245,6 +272,13 @@ export function CompetitionPage() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Trader Config Modal */}
|
||||
<TraderConfigModal
|
||||
isOpen={isModalOpen}
|
||||
onClose={closeModal}
|
||||
traderData={selectedTrader}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -14,9 +14,8 @@ export function Header({ simple = false }: HeaderProps) {
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Left - Logo and Title */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full flex items-center justify-center text-xl"
|
||||
style={{ background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)' }}>
|
||||
⚡
|
||||
<div className="flex items-center justify-center">
|
||||
<img src="/icons/nofx.svg?v=2" alt="NOFX" className="h-10 w-auto" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-xl font-bold" style={{ color: '#EAECEF' }}>
|
||||
|
||||
@@ -57,9 +57,8 @@ export function LoginPage() {
|
||||
<div className="w-full max-w-md">
|
||||
{/* Logo */}
|
||||
<div className="text-center mb-8">
|
||||
<div className="w-16 h-16 rounded-full mx-auto mb-4 flex items-center justify-center text-3xl"
|
||||
style={{ background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)' }}>
|
||||
⚡
|
||||
<div className="w-16 h-16 mx-auto mb-4 flex items-center justify-center">
|
||||
<img src="/icons/nofx.svg?v=2" alt="NOFX" className="w-16 h-16" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold" style={{ color: '#EAECEF' }}>
|
||||
{t('loginTitle', language)}
|
||||
|
||||
@@ -75,9 +75,8 @@ export function RegisterPage() {
|
||||
<div className="w-full max-w-md">
|
||||
{/* Logo */}
|
||||
<div className="text-center mb-8">
|
||||
<div className="w-16 h-16 rounded-full mx-auto mb-4 flex items-center justify-center text-3xl"
|
||||
style={{ background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)' }}>
|
||||
⚡
|
||||
<div className="w-16 h-16 mx-auto mb-4 flex items-center justify-center">
|
||||
<img src="/icons/nofx.svg?v=2" alt="NOFX" className="w-16 h-16" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold" style={{ color: '#EAECEF' }}>
|
||||
{t('appTitle', language)}
|
||||
|
||||
443
web/src/components/TraderConfigModal.tsx
Normal file
443
web/src/components/TraderConfigModal.tsx
Normal file
@@ -0,0 +1,443 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import type { AIModel, Exchange, CreateTraderRequest } from '../types';
|
||||
|
||||
// 提取下划线后面的名称部分
|
||||
function getShortName(fullName: string): string {
|
||||
const parts = fullName.split('_');
|
||||
return parts.length > 1 ? parts[parts.length - 1] : fullName;
|
||||
}
|
||||
|
||||
interface TraderConfigData {
|
||||
trader_id?: string;
|
||||
trader_name: string;
|
||||
ai_model: string;
|
||||
exchange_id: string;
|
||||
btc_eth_leverage: number;
|
||||
altcoin_leverage: number;
|
||||
trading_symbols: string;
|
||||
custom_prompt: string;
|
||||
override_base_prompt: boolean;
|
||||
is_cross_margin: boolean;
|
||||
use_coin_pool: boolean;
|
||||
use_oi_top: boolean;
|
||||
initial_balance: number;
|
||||
}
|
||||
|
||||
interface TraderConfigModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
traderData?: TraderConfigData | null;
|
||||
isEditMode?: boolean;
|
||||
availableModels?: AIModel[];
|
||||
availableExchanges?: Exchange[];
|
||||
onSave?: (data: CreateTraderRequest) => Promise<void>;
|
||||
}
|
||||
|
||||
export function TraderConfigModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
traderData,
|
||||
isEditMode = false,
|
||||
availableModels = [],
|
||||
availableExchanges = [],
|
||||
onSave
|
||||
}: TraderConfigModalProps) {
|
||||
const [formData, setFormData] = useState<TraderConfigData>({
|
||||
trader_name: '',
|
||||
ai_model: '',
|
||||
exchange_id: '',
|
||||
btc_eth_leverage: 5,
|
||||
altcoin_leverage: 3,
|
||||
trading_symbols: '',
|
||||
custom_prompt: '',
|
||||
override_base_prompt: false,
|
||||
is_cross_margin: true,
|
||||
use_coin_pool: false,
|
||||
use_oi_top: false,
|
||||
initial_balance: 1000,
|
||||
});
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [availableCoins, setAvailableCoins] = useState<string[]>([]);
|
||||
const [selectedCoins, setSelectedCoins] = useState<string[]>([]);
|
||||
const [showCoinSelector, setShowCoinSelector] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (traderData) {
|
||||
setFormData(traderData);
|
||||
// 设置已选择的币种
|
||||
if (traderData.trading_symbols) {
|
||||
const coins = traderData.trading_symbols.split(',').map(s => s.trim()).filter(s => s);
|
||||
setSelectedCoins(coins);
|
||||
}
|
||||
} else if (!isEditMode) {
|
||||
setFormData({
|
||||
trader_name: '',
|
||||
ai_model: availableModels[0]?.id || '',
|
||||
exchange_id: availableExchanges[0]?.id || '',
|
||||
btc_eth_leverage: 5,
|
||||
altcoin_leverage: 3,
|
||||
trading_symbols: '',
|
||||
custom_prompt: '',
|
||||
override_base_prompt: false,
|
||||
is_cross_margin: true,
|
||||
use_coin_pool: false,
|
||||
use_oi_top: false,
|
||||
initial_balance: 1000,
|
||||
});
|
||||
}
|
||||
}, [traderData, isEditMode, availableModels, availableExchanges]);
|
||||
|
||||
// 获取系统配置中的币种列表
|
||||
useEffect(() => {
|
||||
const fetchConfig = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/config');
|
||||
const config = await response.json();
|
||||
if (config.default_coins) {
|
||||
setAvailableCoins(config.default_coins);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch config:', error);
|
||||
// 使用默认币种列表
|
||||
setAvailableCoins(['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT', 'DOGEUSDT', 'ADAUSDT']);
|
||||
}
|
||||
};
|
||||
fetchConfig();
|
||||
}, []);
|
||||
|
||||
// 当选择的币种改变时,更新输入框
|
||||
useEffect(() => {
|
||||
const symbolsString = selectedCoins.join(',');
|
||||
setFormData(prev => ({ ...prev, trading_symbols: symbolsString }));
|
||||
}, [selectedCoins]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
const handleInputChange = (field: keyof TraderConfigData, value: any) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
|
||||
// 如果是直接编辑trading_symbols,同步更新selectedCoins
|
||||
if (field === 'trading_symbols') {
|
||||
const coins = value.split(',').map((s: string) => s.trim()).filter((s: string) => s);
|
||||
setSelectedCoins(coins);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCoinToggle = (coin: string) => {
|
||||
setSelectedCoins(prev => {
|
||||
if (prev.includes(coin)) {
|
||||
return prev.filter(c => c !== coin);
|
||||
} else {
|
||||
return [...prev, coin];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!onSave) return;
|
||||
|
||||
setIsSaving(true);
|
||||
try {
|
||||
const saveData: CreateTraderRequest = {
|
||||
name: formData.trader_name,
|
||||
ai_model_id: formData.ai_model,
|
||||
exchange_id: formData.exchange_id,
|
||||
btc_eth_leverage: formData.btc_eth_leverage,
|
||||
altcoin_leverage: formData.altcoin_leverage,
|
||||
trading_symbols: formData.trading_symbols,
|
||||
custom_prompt: formData.custom_prompt,
|
||||
override_base_prompt: formData.override_base_prompt,
|
||||
is_cross_margin: formData.is_cross_margin,
|
||||
use_coin_pool: formData.use_coin_pool,
|
||||
use_oi_top: formData.use_oi_top,
|
||||
initial_balance: formData.initial_balance,
|
||||
};
|
||||
await onSave(saveData);
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 backdrop-blur-sm">
|
||||
<div
|
||||
className="bg-[#1E2329] border border-[#2B3139] rounded-xl shadow-2xl max-w-3xl w-full mx-4 max-h-[90vh] overflow-y-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-6 border-b border-[#2B3139] bg-gradient-to-r from-[#1E2329] to-[#252B35]">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-[#F0B90B] to-[#E1A706] flex items-center justify-center">
|
||||
<span className="text-lg">{isEditMode ? '✏️' : '➕'}</span>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-[#EAECEF]">
|
||||
{isEditMode ? '修改交易员' : '创建交易员'}
|
||||
</h2>
|
||||
<p className="text-sm text-[#848E9C] mt-1">
|
||||
{isEditMode ? '修改交易员配置参数' : '配置新的AI交易员'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="w-8 h-8 rounded-lg text-[#848E9C] hover:text-[#EAECEF] hover:bg-[#2B3139] transition-colors flex items-center justify-center"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-6 space-y-8">
|
||||
{/* Basic Info */}
|
||||
<div className="bg-[#0B0E11] border border-[#2B3139] rounded-lg p-5">
|
||||
<h3 className="text-lg font-semibold text-[#EAECEF] mb-5 flex items-center gap-2">
|
||||
🤖 基础配置
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="text-sm text-[#EAECEF] block mb-2">交易员名称</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.trader_name}
|
||||
onChange={(e) => handleInputChange('trader_name', e.target.value)}
|
||||
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
|
||||
placeholder="请输入交易员名称"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-[#EAECEF] block mb-2">AI模型</label>
|
||||
<select
|
||||
value={formData.ai_model}
|
||||
onChange={(e) => handleInputChange('ai_model', e.target.value)}
|
||||
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
|
||||
>
|
||||
{availableModels.map(model => (
|
||||
<option key={model.id} value={model.id}>
|
||||
{getShortName(model.name || model.id).toUpperCase()}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-[#EAECEF] block mb-2">交易所</label>
|
||||
<select
|
||||
value={formData.exchange_id}
|
||||
onChange={(e) => handleInputChange('exchange_id', e.target.value)}
|
||||
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
|
||||
>
|
||||
{availableExchanges.map(exchange => (
|
||||
<option key={exchange.id} value={exchange.id}>
|
||||
{getShortName(exchange.name || exchange.id).toUpperCase()}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Trading Configuration */}
|
||||
<div className="bg-[#0B0E11] border border-[#2B3139] rounded-lg p-5">
|
||||
<h3 className="text-lg font-semibold text-[#EAECEF] mb-5 flex items-center gap-2">
|
||||
⚖️ 交易配置
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
{/* 第一行:保证金模式和初始余额 */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-[#EAECEF] block mb-2">保证金模式</label>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleInputChange('is_cross_margin', true)}
|
||||
className={`flex-1 px-3 py-2 rounded text-sm ${
|
||||
formData.is_cross_margin
|
||||
? 'bg-[#F0B90B] text-black'
|
||||
: 'bg-[#0B0E11] text-[#848E9C] border border-[#2B3139]'
|
||||
}`}
|
||||
>
|
||||
全仓
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleInputChange('is_cross_margin', false)}
|
||||
className={`flex-1 px-3 py-2 rounded text-sm ${
|
||||
!formData.is_cross_margin
|
||||
? 'bg-[#F0B90B] text-black'
|
||||
: 'bg-[#0B0E11] text-[#848E9C] border border-[#2B3139]'
|
||||
}`}
|
||||
>
|
||||
逐仓
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-[#EAECEF] block mb-2">初始余额 ($)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.initial_balance}
|
||||
onChange={(e) => handleInputChange('initial_balance', Number(e.target.value))}
|
||||
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
|
||||
min="100"
|
||||
step="100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 第二行:杠杆设置 */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-[#EAECEF] block mb-2">BTC/ETH 杠杆</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.btc_eth_leverage}
|
||||
onChange={(e) => handleInputChange('btc_eth_leverage', Number(e.target.value))}
|
||||
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
|
||||
min="1"
|
||||
max="125"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-[#EAECEF] block mb-2">山寨币杠杆</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.altcoin_leverage}
|
||||
onChange={(e) => handleInputChange('altcoin_leverage', Number(e.target.value))}
|
||||
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
|
||||
min="1"
|
||||
max="75"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 第三行:交易币种 */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="text-sm text-[#EAECEF]">交易币种 (用逗号分隔,留空使用默认)</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowCoinSelector(!showCoinSelector)}
|
||||
className="px-3 py-1 text-xs bg-[#F0B90B] text-black rounded hover:bg-[#E1A706] transition-colors"
|
||||
>
|
||||
{showCoinSelector ? '收起选择' : '快速选择'}
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.trading_symbols}
|
||||
onChange={(e) => handleInputChange('trading_symbols', e.target.value)}
|
||||
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
|
||||
placeholder="例如: BTCUSDT,ETHUSDT,ADAUSDT"
|
||||
/>
|
||||
|
||||
{/* 币种选择器 */}
|
||||
{showCoinSelector && (
|
||||
<div className="mt-3 p-3 bg-[#0B0E11] border border-[#2B3139] rounded">
|
||||
<div className="text-xs text-[#848E9C] mb-2">点击选择币种:</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{availableCoins.map(coin => (
|
||||
<button
|
||||
key={coin}
|
||||
type="button"
|
||||
onClick={() => handleCoinToggle(coin)}
|
||||
className={`px-2 py-1 text-xs rounded transition-colors ${
|
||||
selectedCoins.includes(coin)
|
||||
? 'bg-[#F0B90B] text-black'
|
||||
: 'bg-[#1E2329] text-[#848E9C] border border-[#2B3139] hover:border-[#F0B90B]'
|
||||
}`}
|
||||
>
|
||||
{coin.replace('USDT', '')}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Signal Sources */}
|
||||
<div className="bg-[#0B0E11] border border-[#2B3139] rounded-lg p-5">
|
||||
<h3 className="text-lg font-semibold text-[#EAECEF] mb-5 flex items-center gap-2">
|
||||
📡 信号源配置
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.use_coin_pool}
|
||||
onChange={(e) => handleInputChange('use_coin_pool', e.target.checked)}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<label className="text-sm text-[#EAECEF]">使用 Coin Pool 信号</label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.use_oi_top}
|
||||
onChange={(e) => handleInputChange('use_oi_top', e.target.checked)}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<label className="text-sm text-[#EAECEF]">使用 OI Top 信号</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Trading Prompt */}
|
||||
<div className="bg-[#0B0E11] border border-[#2B3139] rounded-lg p-5">
|
||||
<h3 className="text-lg font-semibold text-[#EAECEF] mb-5 flex items-center gap-2">
|
||||
💬 交易策略提示词
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.override_base_prompt}
|
||||
onChange={(e) => handleInputChange('override_base_prompt', e.target.checked)}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<label className="text-sm text-[#EAECEF]">覆盖默认提示词</label>
|
||||
<span className="text-xs text-[#F0B90B]">⚠️ 启用后将完全替换默认策略</span>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-[#EAECEF] block mb-2">
|
||||
{formData.override_base_prompt ? '自定义提示词' : '附加提示词'}
|
||||
</label>
|
||||
<textarea
|
||||
value={formData.custom_prompt}
|
||||
onChange={(e) => handleInputChange('custom_prompt', e.target.value)}
|
||||
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none h-24 resize-none"
|
||||
placeholder={formData.override_base_prompt ? "输入完整的交易策略提示词..." : "输入额外的交易策略提示..."}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex justify-end gap-3 p-6 border-t border-[#2B3139] bg-gradient-to-r from-[#1E2329] to-[#252B35]">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-6 py-3 bg-[#2B3139] text-[#EAECEF] rounded-lg hover:bg-[#404750] transition-all duration-200 border border-[#404750]"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
{onSave && (
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={isSaving || !formData.trader_name || !formData.ai_model || !formData.exchange_id}
|
||||
className="px-8 py-3 bg-gradient-to-r from-[#F0B90B] to-[#E1A706] text-black rounded-lg hover:from-[#E1A706] hover:to-[#D4951E] transition-all duration-200 disabled:bg-[#848E9C] disabled:cursor-not-allowed font-medium shadow-lg"
|
||||
>
|
||||
{isSaving ? '保存中...' : (isEditMode ? '保存修改' : '创建交易员')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,7 @@ export type Language = 'en' | 'zh';
|
||||
export const translations = {
|
||||
en: {
|
||||
// Header
|
||||
appTitle: 'AI Trading System',
|
||||
appTitle: 'NOFX',
|
||||
subtitle: 'Multi-AI Model Trading Platform',
|
||||
aiTraders: 'AI Traders',
|
||||
details: 'Details',
|
||||
@@ -64,11 +64,17 @@ export const translations = {
|
||||
recent: 'Recent',
|
||||
allData: 'All Data',
|
||||
cycles: 'Cycles',
|
||||
|
||||
// Comparison Chart
|
||||
comparisonMode: 'Comparison Mode',
|
||||
dataPoints: 'Data Points',
|
||||
currentGap: 'Current Gap',
|
||||
count: '{count} pts',
|
||||
|
||||
// Competition Page
|
||||
aiCompetition: 'AI Competition',
|
||||
traders: 'traders',
|
||||
liveBattle: 'Qwen vs DeepSeek · Live Battle',
|
||||
liveBattle: 'Live Battle',
|
||||
leader: 'Leader',
|
||||
leaderboard: 'Leaderboard',
|
||||
live: 'LIVE',
|
||||
@@ -147,6 +153,38 @@ export const translations = {
|
||||
useTestnet: 'Use Testnet',
|
||||
enabled: 'Enabled',
|
||||
save: 'Save',
|
||||
|
||||
// AI Model Configuration
|
||||
officialAPI: 'Official API',
|
||||
customAPI: 'Custom API',
|
||||
apiKey: 'API Key',
|
||||
customAPIURL: 'Custom API URL',
|
||||
enterAPIKey: 'Enter API Key',
|
||||
enterCustomAPIURL: 'Enter custom API endpoint URL',
|
||||
useOfficialAPI: 'Use official API service',
|
||||
useCustomAPI: 'Use custom API endpoint',
|
||||
|
||||
// Trader Configuration
|
||||
positionMode: 'Position Mode',
|
||||
crossMarginMode: 'Cross Margin',
|
||||
isolatedMarginMode: 'Isolated Margin',
|
||||
crossMarginDescription: 'Cross margin: All positions share account balance as collateral',
|
||||
isolatedMarginDescription: 'Isolated margin: Each position manages collateral independently, risk isolation',
|
||||
leverageConfiguration: 'Leverage Configuration',
|
||||
btcEthLeverage: 'BTC/ETH Leverage',
|
||||
altcoinLeverage: 'Altcoin Leverage',
|
||||
leverageRecommendation: 'Recommended: BTC/ETH 5-10x, Altcoins 3-5x for risk control',
|
||||
tradingSymbols: 'Trading Symbols',
|
||||
tradingSymbolsPlaceholder: 'Enter symbols, comma separated (e.g., BTCUSDT,ETHUSDT,SOLUSDT)',
|
||||
selectSymbols: 'Select Symbols',
|
||||
selectTradingSymbols: 'Select Trading Symbols',
|
||||
selectedSymbolsCount: 'Selected {count} symbols',
|
||||
clearSelection: 'Clear All',
|
||||
confirmSelection: 'Confirm',
|
||||
tradingSymbolsDescription: 'Empty = use default symbols. Must end with USDT (e.g., BTCUSDT, ETHUSDT)',
|
||||
btcEthLeverageValidation: 'BTC/ETH leverage must be between 1-50x',
|
||||
altcoinLeverageValidation: 'Altcoin leverage must be between 1-20x',
|
||||
invalidSymbolFormat: 'Invalid symbol format: {symbol}, must end with USDT',
|
||||
|
||||
// Loading & Error
|
||||
loading: 'Loading...',
|
||||
@@ -207,7 +245,7 @@ export const translations = {
|
||||
},
|
||||
zh: {
|
||||
// Header
|
||||
appTitle: 'AI交易系统',
|
||||
appTitle: 'NOFX',
|
||||
subtitle: '多AI模型交易平台',
|
||||
aiTraders: 'AI交易员',
|
||||
details: '详情',
|
||||
@@ -268,22 +306,28 @@ export const translations = {
|
||||
recent: '最近',
|
||||
allData: '全部数据',
|
||||
cycles: '个',
|
||||
|
||||
// Comparison Chart
|
||||
comparisonMode: '对比模式',
|
||||
dataPoints: '数据点数',
|
||||
currentGap: '当前差距',
|
||||
count: '{count} 个',
|
||||
|
||||
// Competition Page
|
||||
aiCompetition: 'AI竞赛',
|
||||
traders: '位交易者',
|
||||
liveBattle: 'Qwen vs DeepSeek · 实时对战',
|
||||
leader: '🥇 领先者',
|
||||
leaderboard: '🥇 排行榜',
|
||||
live: '直播',
|
||||
performanceComparison: '📈 表现对比',
|
||||
realTimePnL: '实时盈亏百分比',
|
||||
headToHead: '⚔️ 正面对决',
|
||||
traders: '交易员',
|
||||
liveBattle: '实时对战',
|
||||
leader: '领先者',
|
||||
leaderboard: '排行榜',
|
||||
live: '实时',
|
||||
performanceComparison: '表现对比',
|
||||
realTimePnL: '实时收益率',
|
||||
headToHead: '正面对决',
|
||||
leadingBy: '领先 {gap}%',
|
||||
behindBy: '落后 {gap}%',
|
||||
equity: '净值',
|
||||
pnl: '盈亏',
|
||||
pos: '仓位',
|
||||
equity: '权益',
|
||||
pnl: '收益',
|
||||
pos: '持仓',
|
||||
|
||||
// AI Learning
|
||||
aiLearning: 'AI学习与反思',
|
||||
@@ -351,6 +395,38 @@ export const translations = {
|
||||
useTestnet: '使用测试网',
|
||||
enabled: '启用',
|
||||
save: '保存',
|
||||
|
||||
// AI Model Configuration
|
||||
officialAPI: '官方API',
|
||||
customAPI: '自定义API',
|
||||
apiKey: 'API密钥',
|
||||
customAPIURL: '自定义API地址',
|
||||
enterAPIKey: '请输入API密钥',
|
||||
enterCustomAPIURL: '请输入自定义API端点地址',
|
||||
useOfficialAPI: '使用官方API服务',
|
||||
useCustomAPI: '使用自定义API端点',
|
||||
|
||||
// Trader Configuration
|
||||
positionMode: '仓位模式',
|
||||
crossMarginMode: '全仓模式',
|
||||
isolatedMarginMode: '逐仓模式',
|
||||
crossMarginDescription: '全仓模式:所有仓位共享账户余额作为保证金',
|
||||
isolatedMarginDescription: '逐仓模式:每个仓位独立管理保证金,风险隔离',
|
||||
leverageConfiguration: '杠杆配置',
|
||||
btcEthLeverage: 'BTC/ETH杠杆',
|
||||
altcoinLeverage: '山寨币杠杆',
|
||||
leverageRecommendation: '推荐:BTC/ETH 5-10倍,山寨币 3-5倍,控制风险',
|
||||
tradingSymbols: '交易币种',
|
||||
tradingSymbolsPlaceholder: '输入币种,逗号分隔(如:BTCUSDT,ETHUSDT,SOLUSDT)',
|
||||
selectSymbols: '选择币种',
|
||||
selectTradingSymbols: '选择交易币种',
|
||||
selectedSymbolsCount: '已选择 {count} 个币种',
|
||||
clearSelection: '清空选择',
|
||||
confirmSelection: '确认选择',
|
||||
tradingSymbolsDescription: '留空 = 使用默认币种。必须以USDT结尾(如:BTCUSDT, ETHUSDT)',
|
||||
btcEthLeverageValidation: 'BTC/ETH杠杆必须在1-50倍之间',
|
||||
altcoinLeverageValidation: '山寨币杠杆必须在1-20倍之间',
|
||||
invalidSymbolFormat: '无效的币种格式:{symbol},必须以USDT结尾',
|
||||
|
||||
// Loading & Error
|
||||
loading: '加载中...',
|
||||
|
||||
@@ -82,6 +82,24 @@ export const api = {
|
||||
if (!res.ok) throw new Error('更新自定义策略失败');
|
||||
},
|
||||
|
||||
async getTraderConfig(traderId: string): Promise<any> {
|
||||
const res = await fetch(`${API_BASE}/traders/${traderId}/config`, {
|
||||
headers: getAuthHeaders(),
|
||||
});
|
||||
if (!res.ok) throw new Error('获取交易员配置失败');
|
||||
return res.json();
|
||||
},
|
||||
|
||||
async updateTrader(traderId: string, request: CreateTraderRequest): Promise<TraderInfo> {
|
||||
const res = await fetch(`${API_BASE}/traders/${traderId}`, {
|
||||
method: 'PUT',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
if (!res.ok) throw new Error('更新交易员失败');
|
||||
return res.json();
|
||||
},
|
||||
|
||||
// AI模型配置接口
|
||||
async getModelConfigs(): Promise<AIModel[]> {
|
||||
const res = await fetch(`${API_BASE}/models`, {
|
||||
@@ -242,4 +260,25 @@ export const api = {
|
||||
if (!res.ok) throw new Error('获取竞赛数据失败');
|
||||
return res.json();
|
||||
},
|
||||
|
||||
// 用户信号源配置接口
|
||||
async getUserSignalSource(): Promise<{coin_pool_url: string, oi_top_url: string}> {
|
||||
const res = await fetch(`${API_BASE}/user/signal-sources`, {
|
||||
headers: getAuthHeaders(),
|
||||
});
|
||||
if (!res.ok) throw new Error('获取用户信号源配置失败');
|
||||
return res.json();
|
||||
},
|
||||
|
||||
async saveUserSignalSource(coinPoolUrl: string, oiTopUrl: string): Promise<void> {
|
||||
const res = await fetch(`${API_BASE}/user/signal-sources`, {
|
||||
method: 'POST',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({
|
||||
coin_pool_url: coinPoolUrl,
|
||||
oi_top_url: oiTopUrl,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) throw new Error('保存用户信号源配置失败');
|
||||
},
|
||||
};
|
||||
|
||||
@@ -100,6 +100,7 @@ export interface AIModel {
|
||||
provider: string;
|
||||
enabled: boolean;
|
||||
apiKey?: string;
|
||||
customApiUrl?: string;
|
||||
}
|
||||
|
||||
export interface Exchange {
|
||||
@@ -123,9 +124,14 @@ export interface CreateTraderRequest {
|
||||
ai_model_id: string;
|
||||
exchange_id: string;
|
||||
initial_balance: number;
|
||||
btc_eth_leverage?: number;
|
||||
altcoin_leverage?: number;
|
||||
trading_symbols?: string;
|
||||
custom_prompt?: string;
|
||||
override_base_prompt?: boolean;
|
||||
is_cross_margin?: boolean;
|
||||
use_coin_pool?: boolean;
|
||||
use_oi_top?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateModelConfigRequest {
|
||||
@@ -133,6 +139,7 @@ export interface UpdateModelConfigRequest {
|
||||
[key: string]: {
|
||||
enabled: boolean;
|
||||
api_key: string;
|
||||
custom_api_url?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -159,6 +166,7 @@ export interface CompetitionTraderData {
|
||||
trader_id: string;
|
||||
trader_name: string;
|
||||
ai_model: string;
|
||||
exchange: string;
|
||||
total_equity: number;
|
||||
total_pnl: number;
|
||||
total_pnl_pct: number;
|
||||
|
||||
Reference in New Issue
Block a user